diff options
Diffstat (limited to 'libblkid/libfdisk/src')
23 files changed, 17042 insertions, 0 deletions
diff --git a/libblkid/libfdisk/src/.gitignore b/libblkid/libfdisk/src/.gitignore new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/libblkid/libfdisk/src/.gitignore diff --git a/libblkid/libfdisk/src/Makemodule.am b/libblkid/libfdisk/src/Makemodule.am new file mode 100644 index 000000000..18ddec739 --- /dev/null +++ b/libblkid/libfdisk/src/Makemodule.am @@ -0,0 +1,112 @@ + +# libfdisk.h is generated, so it's stored in builddir! +fdiskincdir = $(includedir)/libfdisk +nodist_fdiskinc_HEADERS = $(top_builddir)/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/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/sun.c \ + libfdisk/src/sgi.c \ + libfdisk/src/dos.c \ + libfdisk/src/bsd.c \ + libfdisk/src/gpt.c \ + $(nodist_fdiskinc_HEADERS) + + +nodist_libfdisk_la_SOURCES = libfdisk/src/fdiskP.h + +libfdisk_la_LIBADD = libcommon.la libuuid.la + +libfdisk_la_CFLAGS = \ + $(SOLIB_CFLAGS) \ + -I$(ul_libuuid_incdir) \ + -I$(ul_libfdisk_incdir) \ + -I$(top_srcdir)/libfdisk/src + +libfdisk_la_DEPENDENCIES = \ + libcommon.la \ + libuuid.la \ + libfdisk/src/libfdisk.sym \ + libfdisk/src/libfdisk.h.in + +libfdisk_la_LDFLAGS = \ + $(SOLIB_LDFLAGS) \ + -Wl,--version-script=$(top_srcdir)/libfdisk/src/libfdisk.sym \ + -version-info $(LIBFDISK_VERSION_INFO) + +if BUILD_LIBBLKID +libfdisk_la_LIBADD += libblkid.la +libfdisk_la_DEPENDENCIES += libblkid.la +libfdisk_la_CFLAGS += -I$(ul_libblkid_incdir) +endif + +EXTRA_DIST += \ + libfdisk/src/libfdisk.sym \ + libfdisk/src/libfdisk.h.in + +if BUILD_LIBFDISK_TESTS +check_PROGRAMS += \ + test_fdisk_ask \ + test_fdisk_script \ + test_fdisk_utils + +libfdisk_tests_cflags = -DTEST_PROGRAM $(libfdisk_la_CFLAGS) +libfdisk_tests_ldflags = libuuid.la -static +libfdisk_tests_ldadd = libfdisk.la $(UUID_LIBS) + +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_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) + +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/libblkid/libfdisk/src/alignment.c b/libblkid/libfdisk/src/alignment.c new file mode 100644 index 000000000..67f1ddd08 --- /dev/null +++ b/libblkid/libfdisk/src/alignment.c @@ -0,0 +1,654 @@ + +#ifdef HAVE_LIBBLKID +#include <blkid.h> +#endif +#include "blkdev.h" + +#include "fdiskP.h" + +/** + * SECTION: alignment + * @title: Alignment + * @short_description: functions to align partitions and work with disk topology and geometry + * + * The libfdisk aligns the end of the partitions to make it possible to align + * the next partition to the "grain" (see fdisk_get_grain()). 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, fdisk_sector_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 - 1); + + return !((granularity + cxt->alignment_offset - offset) & (granularity - 1)); +} + +/* + * 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 - 1); + + return !((granularity + cxt->alignment_offset - offset) & (granularity - 1)); +} + +/** + * 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()). 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 %ju -aligned-to-> %ju", + (uintmax_t) lba, + (uintmax_t) res)); + 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; + + start = fdisk_align_lba(cxt, start, FDISK_ALIGN_UP); + stop = fdisk_align_lba(cxt, stop, FDISK_ALIGN_DOWN); + lba = fdisk_align_lba(cxt, lba, FDISK_ALIGN_NEAREST); + + if (lba < start) + res = start; + else if (lba > stop) + res = stop; + else + res = lba; + + DBG(CXT, ul_debugobj(cxt, "LBA %ju range:<%ju..%ju>, result: %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(int fd) +{ + int sect_sz; + + if (!blkdev_get_sector_size(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 reseted. + * + * 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: logicla 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_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_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, "appling user device properties")); + + if (cxt->user_pyh_sector) + cxt->phy_sector_size = cxt->user_pyh_sector; + if (cxt->user_log_sector) + cxt->sector_size = cxt->min_io_size = + cxt->io_size = cxt->user_log_sector; + + 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->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, "*** reseting 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; + + assert(cxt); + assert(cxt->geom.heads == 0); + + DBG(CXT, ul_debugobj(cxt, "%s: discovering geometry...", cxt->dev_path)); + + /* 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)); + + DBG(CXT, ul_debugobj(cxt, "total sectors: %ju (ioctl=%ju)", + (uintmax_t) cxt->total_sectors, + (uintmax_t) nsects)); + + /* what the kernel/bios thinks the geometry is */ + blkdev_get_geometry(cxt->dev_fd, &cxt->geom.heads, (unsigned int *) &cxt->geom.sectors); + + /* 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 IO is optional, default to minimum IO */ + cxt->io_size = cxt->min_io_size; + } + } + blkid_free_probe(pr); +#endif + + cxt->sector_size = get_sector_size(cxt->dev_fd); + 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/min/optimal 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 sectrors, 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; +} + +/** + * 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, "reseting 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 */ + if (cxt->label && cxt->label->op->reset_alignment) + rc = cxt->label->op->reset_alignment(cxt); + + DBG(CXT, ul_debugobj(cxt, "alignment reseted 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; +} + +/** + * 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; + struct stat statbuf; + + assert(cxt); + assert(cxt->dev_fd >= 0); + + i = fstat(cxt->dev_fd, &statbuf); + if (i == 0 && S_ISBLK(statbuf.st_mode)) { + sync(); +#ifdef BLKRRPART + fdisk_info(cxt, _("Calling ioctl() to re-read partition table.")); + i = ioctl(cxt->dev_fd, BLKRRPART); +#else + errno = ENOSYS; + i = 1; +#endif + } + + if (i) { + fdisk_warn(cxt, _("Re-reading the partition table failed.")); + fdisk_info(cxt, _( + "The kernel still uses the old table. The " + "new table will be used at the next reboot " + "or after you run partprobe(8) or kpartx(8).")); + return -errno; + } + + return 0; +} diff --git a/libblkid/libfdisk/src/ask.c b/libblkid/libfdisk/src/ask.c new file mode 100644 index 000000000..7e0c3c218 --- /dev/null +++ b/libblkid/libfdisk/src/ask.c @@ -0,0 +1,1044 @@ + +#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)); + 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 + * + * Incremparts reference counter. + */ +void fdisk_ref_ask(struct fdisk_ask *ask) +{ + if (ask) + ask->refcount++; +} + + +/** + * fdisk_unref_ask: + * @ask: ask instance + * + * De-incremparts 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) ? -ENOMEM : 0; +} + +/** + * 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 (!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_set_relative + * @ask: ask instance + * @relative: 0 or 1 + * + * Inform libfdisk that user specified number in relative notation rather than + * by explicit number. This info allows to fdisk do some optimization (e.g. + * align end of partiton, 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; +} + +/* + * 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 == 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 + 1 > *len) + return NULL; + + ptr += rlen; + + if (rlen > 0 && *len > (size_t) rlen) + *len -= rlen; + else + *len = 0; + + 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->name, + wantnew ? "new" : "used", + cxt->label->nparts_max, + 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: %ju, high: %ju, default: %ju", + num->low, num->hig, num->dfl)); + + if (!rc && !wantnew && num->low == num->hig) { + if (num->low > 0) { + /* only one existing partiton, 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: %ju [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) + * + * Hight-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 parametrs + * + * 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 parametrs + * + * 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 +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/libblkid/libfdisk/src/bsd.c b/libblkid/libfdisk/src/bsd.c new file mode 100644 index 000000000..618a3eef9 --- /dev/null +++ b/libblkid/libfdisk/src/bsd.c @@ -0,0 +1,992 @@ +/* + * Copyright (C) 2007-2013 Karel Zak <kzak@redhat.com> + * + * Based on the original code from fdisk + * written by Bernhard Fastenrath (fasten@informatik.uni-bonn.de) + * with code from the NetBSD disklabel command. + * + * Arnaldo Carvalho de Melo <acme@conectiva.com.br>, March 1999 + * David Huggins-Daines <dhuggins@linuxcare.com>, January 2000 + */ +#include <unistd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <fcntl.h> +#include <errno.h> +#include <sys/param.h> + +#include "nls.h" +#include "blkdev.h" +#include "fdiskP.h" +#include "pt-mbr.h" +#include "pt-bsd.h" +#include "all-io.h" + + +/** + * SECTION: bsd + * @title: BSD + * @short_description: disk label specific functions + * + */ + +static const char *bsd_dktypenames[] = { + "unknown", + "SMD", + "MSCP", + "old DEC", + "SCSI", + "ESDI", + "ST506", + "HP-IB", + "HP-FL", + "type 9", + "floppy", + 0 +}; +#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_list_disklabel(struct fdisk_context *cxt); +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 partiotn 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)); + + 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) { + int org = fdisk_is_details(cxt); + + cxt->label->nparts_cur = d->d_npartitions; + cxt->label->nparts_max = BSD_MAXPARTITIONS; + + fdisk_enable_details(cxt, 1); + bsd_list_disklabel(cxt); + fdisk_enable_details(cxt, org); + } + + 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_list_disklabel(struct fdisk_context *cxt) +{ + struct bsd_disklabel *d = self_disklabel(cxt); + + assert(cxt); + assert(cxt->label); + assert(fdisk_is_label(cxt, BSD)); + + if (fdisk_is_details(cxt)) { + fdisk_info(cxt, "# %s:", cxt->dev_path); + + if ((unsigned) d->d_type < BSD_DKMAXTYPES) + fdisk_info(cxt, _("type: %s"), bsd_dktypenames[d->d_type]); + else + fdisk_info(cxt, _("type: %d"), d->d_type); + + fdisk_info(cxt, _("disk: %.*s"), (int) sizeof(d->d_typename), d->d_typename); + fdisk_info(cxt, _("label: %.*s"), (int) sizeof(d->d_packname), d->d_packname); + + fdisk_info(cxt, _("flags: %s"), + d->d_flags & BSD_D_REMOVABLE ? _(" removable") : + d->d_flags & BSD_D_ECC ? _(" ecc") : + d->d_flags & BSD_D_BADSECT ? _(" badsect") : ""); + + /* On various machines the fields of *lp are short/int/long */ + /* In order to avoid problems, we cast them all to long. */ + fdisk_info(cxt, _("bytes/sector: %ld"), (long) d->d_secsize); + fdisk_info(cxt, _("sectors/track: %ld"), (long) d->d_nsectors); + fdisk_info(cxt, _("tracks/cylinder: %ld"), (long) d->d_ntracks); + fdisk_info(cxt, _("sectors/cylinder: %ld"), (long) d->d_secpercyl); + fdisk_info(cxt, _("cylinders: %ld"), (long) d->d_ncylinders); + fdisk_info(cxt, _("rpm: %d"), d->d_rpm); + fdisk_info(cxt, _("interleave: %d"), d->d_interleave); + fdisk_info(cxt, _("trackskew: %d"), d->d_trackskew); + fdisk_info(cxt, _("cylinderskew: %d"), d->d_cylskew); + fdisk_info(cxt, _("headswitch: %ld (milliseconds)"), (long) d->d_headswitch); + fdisk_info(cxt, _("track-to-track seek: %ld (milliseconds)"), (long) d->d_trkseek); + } + + fdisk_info(cxt, _("partitions: %d"), d->d_npartitions); + + return 0; +} + +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, d->d_nsectors * d->d_ntracks, + 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 short *start, *end; + unsigned short sum = 0; + + start = (unsigned short *) lp; + end = (unsigned short *) &lp->d_partitions[lp->d_npartitions]; + while (start < end) + sum ^= *start++; + 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; + + 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); + + 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, + .list = bsd_list_disklabel, + .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) +{ + struct fdisk_label *lb; + struct fdisk_bsd_label *bsd; + + assert(cxt); + + 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 lb; +} diff --git a/libblkid/libfdisk/src/context.c b/libblkid/libfdisk/src/context.c new file mode 100644 index 000000000..2a4d377e0 --- /dev/null +++ b/libblkid/libfdisk/src/context.c @@ -0,0 +1,1017 @@ +#ifdef HAVE_LIBBLKID +# include <blkid.h> +#endif + +#include "fdiskP.h" + + +/** + * SECTION: context + * @title: Context + * @short_description: stores info about device, labels etc. + * + * The library distinguish between three types of partitioning objects. + * + * on-disk data + * - disk label specific + * - probed and read by disklabel drivers when assign device to the context + * or when switch to another disk label type + * - only fdisk_write_disklabel() modify on-disk data + * + * in-memory data + * - generic data and disklabel specific data stored in struct fdisk_label + * - all partitioning operations are based on in-memory data only + * + * struct fdisk_partition + * - provides abstraction to present partitions to users + * - fdisk_partition is possible to gather to fdisk_table container + * - used as unified template for new partitions + * - the struct fdisk_partition is always completely independent object and + * any change to the object has no effect to in-memory (or on-disk) label data + */ + +/** + * fdisk_new_context: + * + * Returns: newly allocated libfdisk handler + */ +struct fdisk_context *fdisk_new_context(void) +{ + struct fdisk_context *cxt; + + cxt = calloc(1, sizeof(*cxt)); + if (!cxt) + return NULL; + + DBG(CXT, ul_debugobj(cxt, "alloc")); + cxt->dev_fd = -1; + cxt->refcount = 1; + + /* + * 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); + + return cxt; +} + +static int init_nested_from_parent(struct fdisk_context *cxt, int isnew) +{ + struct fdisk_context *parent; + + assert(cxt); + assert(cxt->parent); + + parent = cxt->parent; + + cxt->alignment_offset = parent->alignment_offset; + cxt->ask_cb = parent->ask_cb; + cxt->ask_data = parent->ask_data; + cxt->dev_fd = parent->dev_fd; + cxt->first_lba = parent->first_lba; + cxt->firstsector_bufsz = parent->firstsector_bufsz; + cxt->firstsector = parent->firstsector; + cxt->geom = parent->geom; + cxt->grain = parent->grain; + cxt->io_size = parent->io_size; + cxt->last_lba = parent->last_lba; + cxt->min_io_size = parent->min_io_size; + cxt->optimal_io_size = parent->optimal_io_size; + cxt->phy_sector_size = parent->phy_sector_size; + cxt->readonly = parent->readonly; + cxt->script = parent->script; + fdisk_ref_script(cxt->script); + cxt->sector_size = parent->sector_size; + cxt->total_sectors = parent->total_sectors; + cxt->user_geom = parent->user_geom; + cxt->user_log_sector = parent->user_log_sector; + cxt->user_pyh_sector = parent->user_pyh_sector; + + /* parent <--> nested independent setting, initialize for new nested + * contexts only */ + if (isnew) { + cxt->listonly = parent->listonly; + cxt->display_details = parent->display_details; + cxt->display_in_cyl_units = parent->display_in_cyl_units; + } + + free(cxt->dev_path); + cxt->dev_path = NULL; + + if (parent->dev_path) { + cxt->dev_path = strdup(parent->dev_path); + if (!cxt->dev_path) + return -ENOMEM; + } + + return 0; +} + +/** + * fdisk_new_nested_context: + * @parent: parental context + * @name: optional label name (e.g. "bsd") + * + * Create a new nested fdisk context for nested disk labels (e.g. BSD or PMBR). + * The function also probes for the nested label on the device if device is + * already assigned to parent. + * + * The new context is initialized according to @parent and both context shares + * some settings and file descriptor to the device. The child propagate some + * changes (like fdisk_assign_device()) to parent, but it does not work + * vice-versa. The behavior is undefined if you assign another device to + * parent. + * + * Returns: new context for nested partition table. + */ +struct fdisk_context *fdisk_new_nested_context(struct fdisk_context *parent, + const char *name) +{ + struct fdisk_context *cxt; + struct fdisk_label *lb = NULL; + + assert(parent); + + cxt = calloc(1, sizeof(*cxt)); + if (!cxt) + return NULL; + + DBG(CXT, ul_debugobj(parent, "alloc nested [%p]", cxt)); + cxt->refcount = 1; + + fdisk_ref_context(parent); + cxt->parent = parent; + + if (init_nested_from_parent(cxt, 1) != 0) + return NULL; + + if (name) { + if (strcmp(name, "bsd") == 0) + lb = cxt->labels[ cxt->nlabels++ ] = fdisk_new_bsd_label(cxt); + else if (strcmp(name, "dos") == 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 delallocate 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; + + for (i = 0; i < cxt->nlabels; i++) + if (cxt->labels[i] + && strcmp(cxt->labels[i]->name, name) == 0) + return cxt->labels[i]; + + DBG(CXT, ul_debugobj(cxt, "failed to found %s label driver", name)); + return NULL; +} + +/** + * fdisk_next_label: + * @cxt: context instance + * @lb: returns pointer to the next label + * + * <informalexample> + * <programlisting> + * // print all supported labels + * struct fdisk_context *cxt = fdisk_new_context(); + * struct fdisk_label *lb = NULL; + * + * while (fdisk_next_label(cxt, &lb) == 0) + * print("label name: %s\n", fdisk_label_get_name(lb)); + * fdisk_unref_context(cxt); + * </programlisting> + * </informalexample> + * + * Returns: <0 in case of error, 0 on success, 1 at the end. + */ +int fdisk_next_label(struct fdisk_context *cxt, struct fdisk_label **lb) +{ + size_t i; + struct fdisk_label *res = NULL; + + if (!lb || !cxt) + return -EINVAL; + + if (!*lb) + res = cxt->labels[0]; + else { + for (i = 1; i < cxt->nlabels; i++) { + if (*lb == cxt->labels[i - 1]) { + res = cxt->labels[i]; + break; + } + } + } + + *lb = res; + return res ? 0 : 1; +} + +/** + * fdisk_get_nlabels: + * @cxt: context + * + * Returns: number of supported label types + */ +size_t fdisk_get_nlabels(struct fdisk_context *cxt) +{ + return cxt ? cxt->nlabels : 0; +} + +int __fdisk_switch_label(struct fdisk_context *cxt, struct fdisk_label *lb) +{ + if (!lb || !cxt) + return -EINVAL; + if (lb->disabled) { + DBG(CXT, ul_debugobj(cxt, "*** attempt to switch to disabled label %s -- ignore!", lb->name)); + return -EINVAL; + } + cxt->label = lb; + DBG(CXT, ul_debugobj(cxt, "--> switching context to %s!", lb->name)); + 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_get_npartitions: + * @cxt: context + * + * The maximal number of the partitions depends on disklabel and does not + * have to describe the real limit of PT. + * + * For example the limit for MBR without extend partition is 4, with extended + * partition it's unlimited (so the function returns the current number of all + * partitions in this case). + * + * And for example for GPT it depends on space allocated on disk for array of + * entry records (usually 128). + * + * It's fine to use fdisk_get_npartitions() in loops, but don't forget that + * partition may be unused (see fdisk_is_partition_used()). + * + * <informalexample> + * <programlisting> + * struct fdisk_partition *pa = NULL; + * size_t i, nmax = fdisk_get_npartitions(cxt); + * + * for (i = 0; i < nmax; i++) { + * if (!fdisk_is_partition_used(cxt, i)) + * continue; + * ... do something ... + * } + * </programlisting> + * </informalexample> + * + * Note that the recommended way to list partitions is to use + * fdisk_get_partitions() and struct fdisk_table than ask disk driver for each + * individual partitions. + * + * Returns: maximal number of partitions for the current label. + */ +size_t fdisk_get_npartitions(struct fdisk_context *cxt) +{ + return cxt && cxt->label ? cxt->label->nparts_max : 0; +} + +/** + * fdisk_is_labeltype: + * @cxt: fdisk context + * @id: FDISK_DISKLABEL_* + * + * See also fdisk_is_label() macro in libfdisk.h. + * + * Returns: return 1 if the current label is @id + */ +int fdisk_is_labeltype(struct fdisk_context *cxt, enum fdisk_labeltype id) +{ + assert(cxt); + + return cxt->label && fdisk_label_get_type(cxt->label) == id; +} + +/** + * fdisk_get_parent: + * @cxt: nested fdisk context + * + * Returns: pointer to parental context, or NULL + */ +struct fdisk_context *fdisk_get_parent(struct fdisk_context *cxt) +{ + assert(cxt); + return cxt->parent; +} + +static void reset_context(struct fdisk_context *cxt) +{ + size_t i; + + DBG(CXT, ul_debugobj(cxt, "*** resetting context")); + + /* reset drives' private data */ + for (i = 0; i < cxt->nlabels; i++) + fdisk_deinit_label(cxt->labels[i]); + + if (cxt->parent) { + /* the first sector may be independent on parent */ + if (cxt->parent->firstsector != cxt->firstsector) + free(cxt->firstsector); + } else { + /* we close device only in primary context */ + if (cxt->dev_fd > -1) + close(cxt->dev_fd); + free(cxt->firstsector); + } + + free(cxt->dev_path); + cxt->dev_path = NULL; + + cxt->dev_fd = -1; + cxt->firstsector = NULL; + cxt->firstsector_bufsz = 0; + + fdisk_zeroize_device_properties(cxt); + + fdisk_unref_script(cxt->script); + cxt->script = NULL; + + cxt->label = NULL; +} + +/* + * This function prints a warning if the device is not wiped (e.g. wipefs(8). + * Please don't call this function if there is already a PT. + * + * Returns: 0 if nothing found, < 0 on error, 1 if found a signature + */ +static int warn_wipe(struct fdisk_context *cxt) +{ +#ifdef HAVE_LIBBLKID + blkid_probe pr; +#endif + int rc = 0; + + assert(cxt); + + if (fdisk_has_label(cxt) || cxt->dev_fd < 0) + return -EINVAL; +#ifdef HAVE_LIBBLKID + DBG(CXT, ul_debugobj(cxt, "wipe check: initialize libblkid prober")); + + pr = blkid_new_probe(); + if (!pr) + return -ENOMEM; + rc = blkid_probe_set_device(pr, cxt->dev_fd, 0, 0); + if (rc) + return rc; + + blkid_probe_enable_superblocks(pr, 1); + blkid_probe_set_superblocks_flags(pr, BLKID_SUBLKS_TYPE); + blkid_probe_enable_partitions(pr, 1); + + /* we care about the first found FS/raid, so don't call blkid_do_probe() + * in loop or don't use blkid_do_fullprobe() ... */ + rc = blkid_do_probe(pr); + if (rc == 0) { + const char *name = NULL; + + if (blkid_probe_lookup_value(pr, "TYPE", &name, 0) == 0 || + blkid_probe_lookup_value(pr, "PTTYPE", &name, 0) == 0) { + fdisk_warnx(cxt, _( + "%s: device contains a valid '%s' signature; it is " + "strongly recommended to wipe the device with " + "wipefs(8) if this is unexpected, in order to " + "avoid possible collisions"), cxt->dev_path, name); + rc = 1; + } + } + + blkid_free_probe(pr); +#endif + 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 and switch + * the current label driver to reflect the probing result. + * + * Note that this function resets all generic setting in context. If the @cxt + * is nested context then the device is assigned to the parental context and + * necessary properties are copied to the @cxt. The change is propagated in + * child->parent direction only. It's impossible to use a different device for + * primary and nested contexts. + * + * Returns: 0 on success, < 0 on error. + */ +int fdisk_assign_device(struct fdisk_context *cxt, + const char *fname, int readonly) +{ + int fd; + + DBG(CXT, ul_debugobj(cxt, "assigning device %s", fname)); + assert(cxt); + + /* redirect request to parent */ + if (cxt->parent) { + int rc, org = fdisk_is_listonly(cxt->parent); + + /* assign_device() is sensitive to "listonly" mode, so let's + * follow the current context setting for the parent to avoid + * unwanted extra warnings. */ + fdisk_enable_listonly(cxt->parent, fdisk_is_listonly(cxt)); + + rc = fdisk_assign_device(cxt->parent, fname, readonly); + fdisk_enable_listonly(cxt->parent, org); + + if (!rc) + rc = init_nested_from_parent(cxt, 0); + if (!rc) + fdisk_probe_labels(cxt); + return rc; + } + + reset_context(cxt); + + fd = open(fname, (readonly ? O_RDONLY : O_RDWR ) | O_CLOEXEC); + if (fd < 0) + return -errno; + + cxt->readonly = readonly; + cxt->dev_fd = fd; + cxt->dev_path = strdup(fname); + if (!cxt->dev_path) + goto fail; + + fdisk_discover_topology(cxt); + fdisk_discover_geometry(cxt); + + if (fdisk_read_firstsector(cxt) < 0) + goto fail; + + /* detect labels and apply labes specific stuff (e.g geomery) + * to the context */ + fdisk_probe_labels(cxt); + + /* let's apply user geometry *after* label prober + * to make it possible to override in-label setting */ + fdisk_apply_user_device_properties(cxt); + + /* warn about obsolete stuff on the device if we aren't in + * list-only mode and there is not PT yet */ + if (!fdisk_is_listonly(cxt) && !fdisk_has_label(cxt)) + warn_wipe(cxt); + + DBG(CXT, ul_debugobj(cxt, "initialized for %s [%s]", + fname, readonly ? "READ-ONLY" : "READ-WRITE")); + return 0; +fail: + DBG(CXT, ul_debugobj(cxt, "failed to assign device")); + return -errno; +} + +/** + * fdisk_deassign_device: + * @cxt: context + * @nosync: disable fsync() + * + * Close device and call fsync(). If the @cxt is nested context than the + * request is redirected to the parent. + * + * Returns: 0 on success, < 0 on error. + */ +int fdisk_deassign_device(struct fdisk_context *cxt, int nosync) +{ + assert(cxt); + assert(cxt->dev_fd >= 0); + + if (cxt->parent) { + int rc = fdisk_deassign_device(cxt->parent, nosync); + + if (!rc) + rc = init_nested_from_parent(cxt, 0); + return rc; + } + + if (cxt->readonly) + close(cxt->dev_fd); + else { + if (fsync(cxt->dev_fd) || close(cxt->dev_fd)) { + fdisk_warn(cxt, _("%s: close device failed"), + cxt->dev_path); + return -errno; + } + + if (!nosync) { + fdisk_info(cxt, _("Syncing disks.")); + sync(); + } + } + + free(cxt->dev_path); + cxt->dev_path = NULL; + + cxt->dev_fd = -1; + + return 0; +} + +/** + * fdisk_is_readonly: + * @cxt: context + * + * Returns: 1 if device open readonly + */ +int fdisk_is_readonly(struct fdisk_context *cxt) +{ + assert(cxt); + return cxt->readonly; +} + +/** + * fdisk_unref_context: + * @cxt: fdisk context + * + * Deallocates context struct. + */ +void fdisk_unref_context(struct fdisk_context *cxt) +{ + int i; + + if (!cxt) + return; + + cxt->refcount--; + if (cxt->refcount <= 0) { + DBG(CXT, ul_debugobj(cxt, "freeing context %p for %s", cxt, cxt->dev_path)); + + reset_context(cxt); /* this is sensitive to parent<->child relationship! */ + + /* deallocate label's private stuff */ + for (i = 0; i < cxt->nlabels; i++) { + if (!cxt->labels[i]) + continue; + if (cxt->labels[i]->op->free) + cxt->labels[i]->op->free(cxt->labels[i]); + else + free(cxt->labels[i]); + } + + fdisk_unref_context(cxt->parent); + cxt->parent = NULL; + + free(cxt); + } +} + + +/** + * fdisk_enable_details: + * @cxt: context + * @enable: true/flase + * + * 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/flase + * + * 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 succes, <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 reseted by fdisk_assign_device(), fdisk_override_geometry() + * and fdisk_reset_alignment(). This is very low level function and library + * does not check if your setting makes any sense. + * + * This function is necessary only when you want to work with very unusual + * partition tables like GPT protective MBR or hybrid partition tables on + * bootable media where the first partition may start on very crazy offsets. + * + * Returns: 0 on success, <0 on error. + */ +fdisk_sector_t fdisk_set_first_lba(struct fdisk_context *cxt, fdisk_sector_t lba) +{ + assert(cxt); + DBG(CXT, ul_debugobj(cxt, "setting first LBA from %ju to %ju", + (uintmax_t) cxt->first_lba, (uintmax_t) lba)); + cxt->first_lba = lba; + return 0; +} + +/** + * fdisk_get_last_lba: + * @cxt: fdisk context + * + * Note that the device has to be already assigned. + * + * Returns: last possible LBA on device + */ +fdisk_sector_t fdisk_get_last_lba(struct fdisk_context *cxt) +{ + return cxt->last_lba; +} + +/** + * fdisk_set_last_lba: + * @cxt: fdisk context + * @lba: last possible logical sector + * + * It's strongly recommended to use the default library setting. The last LBA + * is always reseted 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 and 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_get_nsectors: + * @cxt: context + * + * Returns: size of the device in logical sectors. + */ +fdisk_sector_t fdisk_get_nsectors(struct fdisk_context *cxt) +{ + assert(cxt); + return cxt->total_sectors; +} + +/** + * fdisk_get_devname: + * @cxt: context + * + * Returns: device name. + */ +const char *fdisk_get_devname(struct fdisk_context *cxt) +{ + assert(cxt); + return cxt->dev_path; +} + +/** + * fdisk_get_devfd: + * @cxt: context + * + * Retruns: 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; + + assert(cxt); + + 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/libblkid/libfdisk/src/dos.c b/libblkid/libfdisk/src/dos.c new file mode 100644 index 000000000..2a067076e --- /dev/null +++ b/libblkid/libfdisk/src/dos.c @@ -0,0 +1,2331 @@ +/* + * + * Copyright (C) 2007-2013 Karel Zak <kzak@redhat.com> + * 2012 Davidlohr Bueso <dave@gnu.org> + * + * This is re-written version for libfdisk, the original was fdiskdoslabel.c + * from util-linux fdisk. + */ +#include "c.h" +#include "nls.h" +#include "randutils.h" +#include "pt-mbr.h" +#include "strutils.h" + +#include "fdiskP.h" + +#include <ctype.h> + +#define MAXIMUM_PARTS 60 +#define ACTIVE_FLAG 0x80 + +/** + * SECTION: dos + * @title: DOS (MBR) + * @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" +}; + +#define set_hsc(h,s,c,sector) { \ + s = sector % cxt->geom.sectors + 1; \ + sector /= cxt->geom.sectors; \ + h = sector % cxt->geom.heads; \ + sector /= cxt->geom.heads; \ + c = sector & 0xff; \ + s |= (sector >> 2) & 0xc0; \ + } + + +#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 == 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); + + 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); + + 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; + } + + 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 can not 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 dos_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 partiton %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) { + 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)); + 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; + 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 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); + 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)) { + pe = self_pte(cxt, cxt->label->nparts_max); + + 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); + + clear_partition(pre->ex_entry); + partition_set_changed(cxt, + cxt->label->nparts_max - 1, 1); + 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 (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 (!dos_partition_get_size(p) && + (cxt->label->nparts_max > 5 || q->sys_ind)) { + fdisk_info(cxt, _("omitting empty partition (%zu)"), i+1); + dos_delete_partition(cxt, i); + goto remove; /* numbering changed */ + } + } + + DBG(LABEL, ul_debug("DOS: nparts_max: %zu", cxt->label->nparts_max)); +} + +static int dos_get_disklabel_id(struct fdisk_context *cxt, char **id) +{ + unsigned int num; + + assert(cxt); + assert(id); + assert(cxt->label); + assert(fdisk_is_label(cxt, DOS)); + + num = mbr_get_id(cxt->firstsector); + if (asprintf(id, "0x%08x", num) > 0) + return 0; + + return -ENOMEM; +} + +static int dos_create_disklabel(struct fdisk_context *cxt) +{ + unsigned int id = 0; + int rc, has_id = 0; + + 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 = strtol(s, &end, 16); + if (!errno && end && s < end) + has_id = 1; + } + } + + /* random disk signature */ + if (!has_id) + random_get_bytes(&id, sizeof(id)); + + dos_init(cxt); + rc = fdisk_init_firstsector_buffer(cxt); + if (rc) + return rc; + fdisk_label_set_changed(cxt->label, 1); + + /* Generate an MBR ID for this disk */ + mbr_set_id(cxt->firstsector, id); + + /* Put MBR signature */ + mbr_set_magic(cxt->firstsector); + + fdisk_info(cxt, _("Created a new DOS disklabel with disk " + "identifier 0x%08x."), id); + return 0; +} + +static int dos_set_disklabel_id(struct fdisk_context *cxt) +{ + char *end = NULL, *str = NULL; + unsigned int id, old; + struct fdisk_dos_label *l; + int rc; + + 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); + rc = fdisk_ask_string(cxt, + _("Enter the new disk identifier"), &str); + if (rc) + return rc; + + errno = 0; + id = strtoul(str, &end, 0); + if (errno || str == end || (end && *end)) { + fdisk_warnx(cxt, _("Incorrect value.")); + 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 void get_partition_table_geometry(struct fdisk_context *cxt, + unsigned int *ph, unsigned int *ps) +{ + unsigned char *bufp = cxt->firstsector; + struct dos_partition *p; + int i, h, s, hh, ss; + int first = 1; + int bad = 0; + + hh = ss = 0; + for (i = 0; i < 4; i++) { + p = mbr_get_partition(bufp, i); + if (p->sys_ind != 0) { + h = p->eh + 1; + s = (p->es & 077); + if (first) { + hh = h; + ss = s; + first = 0; + } else if (hh != h || ss != s) + bad = 1; + } + } + + if (!first && !bad) { + *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: reseting alignemnt for DOS-comaptiblem 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; + } + + for (i = 0; i < 4; i++) { + struct pte *pe = self_pte(cxt, i); + + 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); + + 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)); + + 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, stop=%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); + + if (is_dos_compatible(cxt) && (start/(cxt->geom.sectors*cxt->geom.heads) > 1023)) + start = cxt->geom.heads*cxt->geom.sectors*1024 - 1; + set_hsc(p->bh, p->bs, p->bc, start); + if (is_dos_compatible(cxt) && (stop/(cxt->geom.sectors*cxt->geom.heads) > 1023)) + stop = cxt->geom.heads*cxt->geom.sectors*1024 - 1; + set_hsc(p->eh, p->es, p->ec, stop); + partition_set_changed(cxt, i, 1); +} + +static fdisk_sector_t get_unused_start(struct fdisk_context *cxt, + int part_n, fdisk_sector_t start, + fdisk_sector_t first[], fdisk_sector_t last[]) +{ + size_t i; + + for (i = 0; i < cxt->label->nparts_max; i++) { + fdisk_sector_t lastplusoff; + struct pte *pe = self_pte(cxt, i); + + if (start == pe->offset) + start += cxt->first_lba; + lastplusoff = last[i] + ((part_n < 4) ? 0 : cxt->first_lba); + if (start >= first[i] && start <= lastplusoff) + start = lastplusoff + 1; + } + + return start; +} + +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; + + 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] = 0xffffffff; + last[i] = 0; + } else { + first[i] = get_abs_partition_start(pe); + last[i] = get_abs_partition_end(pe); + } + } +} + +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 tepmlate 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; +} + +static fdisk_sector_t get_possible_last(struct fdisk_context *cxt, size_t n) +{ + fdisk_sector_t limit; + + if (n >= 4) { + /* 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 0; + limit = get_abs_partition_end(ext_pe); + } else { + /* primary partitions */ + if (fdisk_use_cylinders(cxt) || !cxt->total_sectors) + limit = cxt->geom.heads * cxt->geom.sectors * cxt->geom.cylinders - 1; + else + limit = cxt->total_sectors - 1; + + if (limit > UINT_MAX) + limit = UINT_MAX; + } + + DBG(LABEL, ul_debug("DOS: last possible sector for #%zu is %ju", + n, (uintmax_t) limit)); + return limit; +} + +/* returns last free sector for area addressed by @start, the first[] and + * last[] are fill_bounds() results */ +static fdisk_sector_t get_unused_last(struct fdisk_context *cxt, size_t n, + fdisk_sector_t start, + fdisk_sector_t first[], fdisk_sector_t last[]) +{ + size_t i; + fdisk_sector_t limit = get_possible_last(cxt, n); + + for (i = 0; i < cxt->label->nparts_max; i++) { + struct pte *pe = self_pte(cxt, i); + + if (start < pe->offset && limit >= pe->offset) + limit = pe->offset - 1; + if (start < first[i] && limit >= first[i]) + limit = first[i] - 1; + } + + DBG(LABEL, ul_debug("DOS: unused sector for #%zu is %ju", + n, (uintmax_t) limit)); + return limit; +} + +static int add_partition(struct fdisk_context *cxt, size_t n, + struct fdisk_partition *pa) +{ + int sys, read = 0, rc, isrel = 0; + size_t i; + struct fdisk_dos_label *l = self_label(cxt); + struct dos_partition *p = self_partition(cxt, n); + struct pte *ext_pe = l->ext_offset ? self_pte(cxt, l->ext_index) : NULL; + + fdisk_sector_t start, stop = 0, limit, temp, + first[cxt->label->nparts_max], + last[cxt->label->nparts_max]; + + DBG(LABEL, ul_debug("DOS: adding partition %zu", n)); + + sys = pa && pa->type ? pa->type->code : MBR_LINUX_DATA_PARTITION; + + if (is_used_partition(p)) { + fdisk_warnx(cxt, _("Partition %zu is already defined. " + "Delete it before re-adding it."), + n + 1); + return -EINVAL; + } + fill_bounds(cxt, first, last); + limit = get_possible_last(cxt, n); + + if (n < 4) { + if (cxt->parent && fdisk_is_label(cxt->parent, GPT)) + start = 1; /* Bad boy modifies hybrid MBR */ + else { + if (cxt->script && pa && fdisk_partition_has_start(pa) + && pa->start < cxt->first_lba + && pa->start >= 1) + fdisk_set_first_lba(cxt, 1); + + start = cxt->first_lba; + } + + if (l->ext_offset) { + assert(ext_pe); + first[l->ext_index] = l->ext_offset; + last[l->ext_index] = get_abs_partition_end(ext_pe); + } + } else { + assert(ext_pe); + + if (cxt->script && pa && fdisk_partition_has_start(pa) + && pa->start >= l->ext_offset + && pa->start < l->ext_offset + cxt->first_lba) + fdisk_set_first_lba(cxt, 1); + + start = l->ext_offset + cxt->first_lba; + } + + if (fdisk_use_cylinders(cxt)) + for (i = 0; i < cxt->label->nparts_max; i++) { + first[i] = (fdisk_cround(cxt, first[i]) - 1) + * fdisk_get_units_per_sector(cxt); + } + + /* + * Ask for first sector + */ + do { + fdisk_sector_t dflt, aligned; + + temp = start; + dflt = start = get_unused_start(cxt, n, start, first, last); + + 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); + dflt = get_unused_start(cxt, n, aligned, first, last); + } 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) { + fdisk_info(cxt, _("Sector %llu is already allocated."), + temp); + temp = start; + read = 0; + if (pa && (fdisk_partition_has_start(pa) || + pa->start_follow_default)) + break; + } + + if (!read && start == temp) { + rc = get_start_from_user(cxt, &start, temp, dflt, limit, pa); + if (rc) + return rc; + read = 1; + } + } 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); + pe->offset = l->ext_offset; + + } else if (n > 4) { + /* The second (and another) EBR */ + struct pte *pe = self_pte(cxt, n); + + pe->offset = start - cxt->first_lba; + if (pe->offset == l->ext_offset) { /* must be corrected */ + pe->offset++; + if (cxt->first_lba == 1) + start++; + } + } + + limit = get_unused_last(cxt, n, start, first, last); + + if (start > limit) { + fdisk_info(cxt, _("No free sectors available.")); + if (n > 4) + cxt->label->nparts_max--; + return -ENOSPC; + } + + /* + * 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 - 1; + isrel = pa->size_explicit ? 0 : 1; + } 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, 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 */ + + rc = fdisk_do_ask(cxt, ask); + stop = fdisk_ask_number_get_result(ask); + isrel = fdisk_ask_number_is_relative(ask); + fdisk_unref_ask(ask); + if (rc) + return rc; + if (fdisk_use_cylinders(cxt)) { + stop = stop * fdisk_get_units_per_sector(cxt) - 1; + if (stop >limit) + stop = limit; + } + } + + DBG(LABEL, ul_debug("DOS: raw stop: %ju", (uintmax_t) stop)); + + if (stop > limit) + stop = limit; + + if (stop < limit) { + if (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) - 1; + if (stop > limit) + stop = limit; + } + } + + set_partition(cxt, n, 0, start, stop, sys, pa && pa->boot == 1 ? 1 : 0); + if (n > 4) { + struct pte *pe = self_pte(cxt, n); + 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); + + l->ext_index = n; + l->ext_offset = start; + pen->ex_entry = p; + } + + fdisk_label_set_changed(cxt->label, 1); + return 0; +} + +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); + + 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 void check(struct fdisk_context *cxt, size_t n, + unsigned int h, unsigned int s, unsigned int c, + unsigned int start) +{ + unsigned int total, real_s, real_c; + + if (!is_dos_compatible(cxt)) + return; + + real_s = sector(s) - 1; + real_c = cylinder(s, c); + total = (real_c * cxt->geom.heads + h) * cxt->geom.sectors + real_s; + + if (!total) + fdisk_warnx(cxt, _("Partition %zu: contains sector 0"), n); + if (h >= cxt->geom.heads) + fdisk_warnx(cxt, _("Partition %zu: head %d greater than " + "maximum %d"), n, h + 1, cxt->geom.heads); + if (real_s >= cxt->geom.sectors) + fdisk_warnx(cxt, _("Partition %zu: sector %d greater than " + "maximum %llu"), n, s, cxt->geom.sectors); + if (real_c >= cxt->geom.cylinders) + fdisk_warnx(cxt, _("Partition %zu: cylinder %d greater than " + "maximum %llu"), + n, real_c + 1, + cxt->geom.cylinders); + + if (cxt->geom.cylinders <= 1024 && start != total) + fdisk_warnx(cxt, _("Partition %zu: previous sectors %u " + "disagrees with total %u"), n, start, total); +} + +/* 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 void 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 */ + + if (!is_dos_compatible(cxt)) + return; + + if (!cxt->geom.heads || !cxt->geom.sectors || (partition >= 4)) + return; /* do not check extended partitions */ + + /* physical beginning c, h, s */ + pbc = (p->bc & 0xff) | ((p->bs << 2) & 0x300); + pbh = p->bh; + pbs = p->bs & 0x3f; + + /* physical ending c, h, s */ + pec = (p->ec & 0xff) | ((p->es << 2) & 0x300); + peh = p->eh; + pes = p->es & 0x3f; + + /* 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 (cxt->geom.cylinders <= 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); + } + + /* Same physical / logical ending? */ + if (cxt->geom.cylinders <= 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); + } + + /* 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); + } +} + +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); + + 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 (is_used_partition(p) && !IS_EXTENDED(p->sys_ind)) { + check_consistency(cxt, p, i); + if (get_abs_partition_start(pe) < first[i]) + fdisk_warnx(cxt, _( + "Partition %zu: bad start-of-data."), + i + 1); + + 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); + + 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); + + e_last = get_abs_partition_end(ext_pe); + + for (i = 4; i < cxt->label->nparts_max; i++) { + total++; + p = self_partition(cxt, i); + + if (!p->sys_ind) { + if (i != 4 || i + 1 < cxt->label->nparts_max) + fdisk_warnx(cxt, + _("Partition %zu: empty."), + i + 1); + } 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); + } + } + } + + if (total > n_sectors) + fdisk_warnx(cxt, _("Total allocated sectors %llu greater " + "than the maximum %llu."), total, n_sectors); + else if (total < n_sectors) + fdisk_warnx(cxt, _("Remaining %lld unallocated %ld-byte " + "sectors."), n_sectors - total, cxt->sector_size); + + return 0; +} + +/* + * 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, free_primary = 0, free_sectors = 0; + fdisk_sector_t last = 0, grain; + int rc = 0; + struct fdisk_dos_label *l; + struct pte *ext_pe; + size_t res; /* partno */ + + assert(cxt); + assert(cxt->label); + assert(fdisk_is_label(cxt, DOS)); + + DBG(LABEL, ul_debug("DOS: new partition wanted")); + + l = self_label(cxt); + ext_pe = l->ext_offset ? self_pte(cxt, l->ext_index) : NULL; + + /* + * partition template (@pa) based partitioning + */ + + /* pa 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", pa)); + rc = add_logical(cxt, pa, &res); + goto done; + + /* pa specifies that extended partition is wanted */ + } else if (pa && pa->type && pa->type->code == MBR_DOS_EXTENDED_PARTITION) { + DBG(LABEL, ul_debug("DOS: pa template %p: add extened", pa)); + if (l->ext_offset) { + 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; + } + + /* pa specifies start, but outside extended partition */ + } else if (pa && fdisk_partition_has_start(pa) && l->ext_offset) { + DBG(LABEL, ul_debug("DOS: pa template %p: add primary", pa)); + rc = get_partition_unused_primary(cxt, pa, &res); + if (rc == 0) { + rc = add_partition(cxt, res, pa); + goto done; + } + } + + /* + * dialog driven partitioning (it does not mean that @pa template is + * completely ignored!) + */ + + /* check if there is space for primary partition */ + grain = cxt->grain > cxt->sector_size ? cxt->grain / cxt->sector_size : 1; + last = cxt->first_lba; + + for (i = 0; i < 4; i++) { + struct dos_partition *p = self_partition(cxt, i); + + if (is_used_partition(p)) { + fdisk_sector_t start = dos_partition_get_start(p); + if (last + grain <= start) + free_sectors = 1; + last = start + dos_partition_get_size(p); + } else + free_primary++; + } + if (last + grain < cxt->total_sectors - 1) + free_sectors = 1; + + if (!free_primary && cxt->label->nparts_max >= MAXIMUM_PARTS) { + fdisk_info(cxt, _("The maximum number of partitions has " + "been created.")); + return -EINVAL; + } + rc = 1; + + if (!free_primary || !free_sectors) { + DBG(LABEL, ul_debug("DOS: primary impossible, add logical")); + if (l->ext_offset) { + if (!pa || fdisk_partition_has_start(pa)) { + if (!free_primary) + fdisk_info(cxt, _("All primary partitions are in use.")); + else if (!free_sectors) + fdisk_info(cxt, _("All space for primary partitions is in use.")); + } + rc = add_logical(cxt, pa, &res); + } else { + fdisk_info(cxt, + _( "Impossible to create another primary partition. " + "If you want to create more partitions, you must " + "replace a primary partition with an extended " + "partition first.")); + return -EINVAL; + } + } else if (cxt->label->nparts_max >= MAXIMUM_PARTS) { + fdisk_info(cxt, _("All logical partitions are in use. " + "Adding a primary partition.")); + rc = get_partition_unused_primary(cxt, pa, &res); + if (rc == 0) + rc = add_partition(cxt, res, pa); + } else { + char hint[BUFSIZ]; + struct fdisk_ask *ask; + int c; + + /* the default layout for scripts is to create primary partitions */ + if (cxt->script) { + 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), + _("%zu primary, %d extended, %zu 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) + return rc; + fdisk_ask_menu_get_result(ask, &c); + fdisk_unref_ask(ask); + + 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: writting 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)); + + 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); + if (pe->changed) + mbr_changed = 1; + } + } + if (mbr_changed) { + 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); + + 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, off_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 (n - 1 + 4 < cxt->label->nparts_max) { + struct pte *pe = self_pte(cxt, n - 1 + 4); + + assert(pe->private_sectorbuffer); + + *name = "EBR"; + *offset = 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 = 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_list_disklabel(struct fdisk_context *cxt) +{ + assert(cxt); + assert(cxt->label); + assert(fdisk_is_label(cxt, DOS)); + + return 0; +} + +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); + 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), + sector(p->bs), + p->bh) < 0) + return -ENOMEM; + + /* end C/H/S */ + if (asprintf(&pa->end_chs, "%d/%d/%d", + cylinder(p->es, p->ec), + sector(p->es), + p->eh) < 0) + return -ENOMEM; + + 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; + 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; + + if (pa->type && IS_EXTENDED(pa->type->code)) { + fdisk_warnx(cxt, _("You cannot change a partition into an " + "extended one or vice versa. Delete it first.")); + return -EINVAL; + } + + if (pa->type && !pa->type->code) + fdisk_warnx(cxt, _("Type 0 means free space to many systems. " + "Having partitions of type 0 is probably unwise.")); + l = self_label(cxt); + p = self_partition(cxt, n); + pe = self_pte(cxt, n); + + 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 (pa->end_follow_default) { + fdisk_sector_t first[cxt->label->nparts_max], + last[cxt->label->nparts_max], + xlast; + struct pte *ext = l->ext_offset ? self_pte(cxt, l->ext_index) : NULL; + + fill_bounds(cxt, first, last); + + if (ext && l->ext_offset) { + first[l->ext_index] = l->ext_offset; + last[l->ext_index] = get_abs_partition_end(ext); + } + if (FDISK_IS_UNDEF(start)) + start = get_abs_partition_start(pe); + + DBG(LABEL, ul_debug("DOS: #%zu now %ju +%ju sectors", + n, (uintmax_t) start, (uintmax_t) dos_partition_get_size(p))); + + xlast = get_unused_last(cxt, n, start, first, last); + size = xlast ? xlast - start + 1: dos_partition_get_size(p); + + DBG(LABEL, ul_debug("DOS: #%zu wanted %ju +%ju sectors", + n, (uintmax_t) start, (uintmax_t) 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, + pa->boot == 1); + } 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 = pa->boot == 1 ? ACTIVE_FLAG : 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); + + 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) +{ + struct pte *ae = (struct pte *) a, + *be = (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); + + 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 tas to be still the same! */ + dos_partition_set_start(cur->pt_entry, nxt_start - cur->offset); + dos_partition_set_start(nxt->pt_entry, cur_start - nxt->offset); + + 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); + + 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)) { + fdisk_info(cxt, _("Nothing to do. Ordering is correct already.")); + return 0; + } + + 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); + + 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); + + fdisk_info(cxt, _("Done.")); + return 0; +} + +/* 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); + 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 default start is at the second sector of the disk or at the + * second sector of the extended partition + */ + free_start = pe->offset ? pe->offset + 1 : 1; + + 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 = 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); + + partition_set_changed(cxt, i, 1); + } + + 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); + + 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, + .list = dos_list_disklabel, + .reorder = dos_reorder, + .get_id = dos_get_disklabel_id, + .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, + + .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) +{ + struct fdisk_label *lb; + struct fdisk_dos_label *dos; + + assert(cxt); + + 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->fields = dos_fields; + lb->nfields = ARRAY_SIZE(dos_fields); + + return lb; +} + +/** + * 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/libblkid/libfdisk/src/fdiskP.h b/libblkid/libfdisk/src/fdiskP.h new file mode 100644 index 000000000..b169a9ffb --- /dev/null +++ b/libblkid/libfdisk/src/fdiskP.h @@ -0,0 +1,438 @@ +/* + * fdiskP.h - private library header file + * + * Copyright (C) 2012 Karel Zak <kzak@redhat.com> + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ + +#ifndef _LIBFDISK_PRIVATE_H +#define _LIBFDISK_PRIVATE_H + +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + + +#include "c.h" +#include "libfdisk.h" + +#include "nls.h" /* temporary before dialog API will be implamented */ +#include "list.h" +#include "debug.h" +#include <stdio.h> +#include <stdarg.h> + +/* features */ +#define CONFIG_LIBFDISK_ASSERT + +#ifdef CONFIG_LIBFDISK_ASSERT +#include <assert.h> +#endif + +/* + * 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_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_) + +#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 + + +/* + * 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)) + +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 */ + + char *name; /* partition name */ + char *uuid; /* partition UUID */ + char *attrs; /* partition flags/attributes converted to string */ + struct fdisk_parttype *type; /* partition type */ + + 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 */ + used : 1, /* partition already used */ + wholedisk : 1; /* special system partition */ +}; + +#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); + /* list disklabel details */ + int (*list)(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, off_t *offset, size_t *size); + /* reorder partitions */ + int (*reorder)(struct fdisk_context *cxt); + + /* get disk label ID */ + int (*get_id)(struct fdisk_context *cxt, char **id); + /* set disk label ID */ + int (*set_id)(struct fdisk_context *cxt); + + + /* 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 defauls 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[] */ + + size_t nparts_max; /* maximal number of partitions */ + size_t nparts_cur; /* number of currently used partitions */ + + int flags; /* FDISK_LABEL_FL_* flags */ + + 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; + } 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 meni item */ + int result; + struct ask_menuitem *first; + } menu; + } data; +}; + +struct fdisk_context { + int dev_fd; /* device descriptor */ + char *dev_path; /* device path */ + 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 */ + listonly : 1; /* list partition, nothing else */ + + /* alignment */ + unsigned long grain; /* alignment unit */ + fdisk_sector_t first_lba; /* recommended begin of the first partition */ + fdisk_sector_t last_lba; /* recomennded 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; + + 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 */ +}; + +/* partition.c */ +int fdisk_partition_next_partno(struct fdisk_partition *pa, + struct fdisk_context *cxt, + size_t *n); + +/* 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_apply_user_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); +extern int fdisk_read_firstsector(struct fdisk_context *cxt); +extern char *fdisk_partname(const char *dev, size_t partno); + +/* label.c */ +extern int fdisk_probe_labels(struct fdisk_context *cxt); +extern void fdisk_deinit_label(struct fdisk_label *lb); + +/* ask.c */ +struct fdisk_ask *fdisk_new_ask(void); +void fdisk_reset_ask(struct fdisk_ask *ask); +int fdisk_ask_set_query(struct fdisk_ask *ask, const char *str); +int fdisk_ask_set_type(struct fdisk_ask *ask, int type); +int fdisk_do_ask(struct fdisk_context *cxt, struct fdisk_ask *ask); +int fdisk_ask_number_set_range(struct fdisk_ask *ask, const char *range); +int fdisk_ask_number_set_default(struct fdisk_ask *ask, uint64_t dflt); +int fdisk_ask_number_set_low(struct fdisk_ask *ask, uint64_t low); +int fdisk_ask_number_set_high(struct fdisk_ask *ask, uint64_t high); +int fdisk_ask_number_set_base(struct fdisk_ask *ask, uint64_t base); +int fdisk_ask_number_set_unit(struct fdisk_ask *ask, uint64_t unit); +int fdisk_ask_number_is_relative(struct fdisk_ask *ask); +int fdisk_ask_menu_set_default(struct fdisk_ask *ask, int dfl); +int fdisk_ask_menu_add_item(struct fdisk_ask *ask, int key, + const char *name, const char *desc); +int fdisk_ask_print_set_errno(struct fdisk_ask *ask, int errnum); +int fdisk_ask_print_set_mesg(struct fdisk_ask *ask, const char *mesg); +int fdisk_info_new_partition( + struct fdisk_context *cxt, + int num, fdisk_sector_t start, fdisk_sector_t stop, + struct fdisk_parttype *t); + +/* dos.c */ +extern struct dos_partition *fdisk_dos_get_partition( + struct fdisk_context *cxt, + size_t i); + +#endif /* _LIBFDISK_PRIVATE_H */ diff --git a/libblkid/libfdisk/src/gpt.c b/libblkid/libfdisk/src/gpt.c new file mode 100644 index 000000000..8c1c96c37 --- /dev/null +++ b/libblkid/libfdisk/src/gpt.c @@ -0,0 +1,2565 @@ +/* + * Copyright (C) 2007 Karel Zak <kzak@redhat.com> + * Copyright (C) 2012 Davidlohr Bueso <dave@gnu.org> + * + * GUID Partition Table (GPT) support. Based on UEFI Specs 2.3.1 + * Chapter 5: GUID Partition Table (GPT) Disk Layout (Jun 27th, 2012). + * Some ideas and inspiration from GNU parted and gptfdisk. + */ +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <inttypes.h> +#include <sys/stat.h> +#include <sys/utsname.h> +#include <sys/types.h> +#include <fcntl.h> +#include <unistd.h> +#include <errno.h> +#include <ctype.h> +#include <uuid.h> + +#include "fdiskP.h" + +#include "nls.h" +#include "crc32.h" +#include "blkdev.h" +#include "bitops.h" +#include "strutils.h" +#include "all-io.h" + +/** + * SECTION: gpt + * @title: UEFI GPT + * @short_description: specific functionality + */ + +#define GPT_HEADER_SIGNATURE 0x5452415020494645LL /* EFI PART */ +#define GPT_HEADER_REVISION_V1_02 0x00010200 +#define GPT_HEADER_REVISION_V1_00 0x00010000 +#define GPT_HEADER_REVISION_V0_99 0x00009900 +#define GPT_HEADER_MINSZ 92 /* bytes */ + +#define GPT_PMBR_LBA 0 +#define GPT_MBR_PROTECTIVE 1 +#define GPT_MBR_HYBRID 2 + +#define GPT_PRIMARY_PARTITION_TABLE_LBA 0x00000001 + +#define EFI_PMBR_OSTYPE 0xEE +#define MSDOS_MBR_SIGNATURE 0xAA55 +#define GPT_PART_NAME_LEN (72 / sizeof(uint16_t)) +#define GPT_NPARTITIONS 128 + +/* Globally unique identifier */ +struct gpt_guid { + uint32_t time_low; + uint16_t time_mid; + uint16_t time_hi_and_version; + uint8_t clock_seq_hi; + uint8_t clock_seq_low; + uint8_t node[6]; +}; + + +/* only checking that the GUID is 0 is enough to verify an empty partition. */ +#define GPT_UNUSED_ENTRY_GUID \ + ((struct gpt_guid) { 0x00000000, 0x0000, 0x0000, 0x00, 0x00, \ + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }}) + +/* Linux native partition type */ +#define GPT_DEFAULT_ENTRY_TYPE "0FC63DAF-8483-4772-8E79-3D69D8477DE4" + +/* + * Attribute bits + */ +enum { + /* UEFI specific */ + GPT_ATTRBIT_REQ = 0, + GPT_ATTRBIT_NOBLOCK = 1, + GPT_ATTRBIT_LEGACY = 2, + + /* GUID specific (range 48..64)*/ + GPT_ATTRBIT_GUID_FIRST = 48, + GPT_ATTRBIT_GUID_COUNT = 16 +}; + +#define GPT_ATTRSTR_REQ "RequiredPartiton" +#define GPT_ATTRSTR_NOBLOCK "NoBlockIOProtocol" +#define GPT_ATTRSTR_LEGACY "LegacyBIOSBootable" + +/* The GPT Partition entry array contains an array of GPT entries. */ +struct gpt_entry { + struct gpt_guid type; /* purpose and type of the partition */ + struct gpt_guid partition_guid; + uint64_t lba_start; + uint64_t lba_end; + uint64_t attrs; + uint16_t name[GPT_PART_NAME_LEN]; +} __attribute__ ((packed)); + +/* GPT header */ +struct gpt_header { + uint64_t signature; /* header identification */ + uint32_t revision; /* header version */ + uint32_t size; /* in bytes */ + uint32_t crc32; /* header CRC checksum */ + uint32_t reserved1; /* must be 0 */ + uint64_t my_lba; /* LBA of block that contains this struct (LBA 1) */ + uint64_t alternative_lba; /* backup GPT header */ + uint64_t first_usable_lba; /* first usable logical block for partitions */ + uint64_t last_usable_lba; /* last usable logical block for partitions */ + struct gpt_guid disk_guid; /* unique disk identifier */ + uint64_t partition_entry_lba; /* LBA of start of partition entries array */ + uint32_t npartition_entries; /* total partition entries - normally 128 */ + uint32_t sizeof_partition_entry; /* bytes for each GUID pt */ + uint32_t partition_entry_array_crc32; /* partition CRC checksum */ + uint8_t reserved2[512 - 92]; /* must all be 0 */ +} __attribute__ ((packed)); + +struct gpt_record { + uint8_t boot_indicator; /* unused by EFI, set to 0x80 for bootable */ + uint8_t start_head; /* unused by EFI, pt start in CHS */ + uint8_t start_sector; /* unused by EFI, pt start in CHS */ + uint8_t start_track; + uint8_t os_type; /* EFI and legacy non-EFI OS types */ + uint8_t end_head; /* unused by EFI, pt end in CHS */ + uint8_t end_sector; /* unused by EFI, pt end in CHS */ + uint8_t end_track; /* unused by EFI, pt end in CHS */ + uint32_t starting_lba; /* used by EFI - start addr of the on disk pt */ + uint32_t size_in_lba; /* used by EFI - size of pt in LBA */ +} __attribute__ ((packed)); + +/* Protected MBR and legacy MBR share same structure */ +struct gpt_legacy_mbr { + uint8_t boot_code[440]; + uint32_t unique_mbr_signature; + uint16_t unknown; + struct gpt_record partition_record[4]; + uint16_t signature; +} __attribute__ ((packed)); + +/* + * Here be dragons! + * See: http://en.wikipedia.org/wiki/GUID_Partition_Table#Partition_type_GUIDs + */ +#define DEF_GUID(_u, _n) \ + { \ + .typestr = (_u), \ + .name = (_n), \ + } + +static struct fdisk_parttype gpt_parttypes[] = +{ + /* Generic OS */ + DEF_GUID("C12A7328-F81F-11D2-BA4B-00A0C93EC93B", N_("EFI System")), + + DEF_GUID("024DEE41-33E7-11D3-9D69-0008C781F39F", N_("MBR partition scheme")), + DEF_GUID("D3BFE2DE-3DAF-11DF-BA40-E3A556D89593", N_("Intel Fast Flash")), + + /* Hah!IdontneedEFI */ + DEF_GUID("21686148-6449-6E6F-744E-656564454649", N_("BIOS boot")), + + /* Windows */ + DEF_GUID("E3C9E316-0B5C-4DB8-817D-F92DF00215AE", N_("Microsoft reserved")), + DEF_GUID("EBD0A0A2-B9E5-4433-87C0-68B6B72699C7", N_("Microsoft basic data")), + DEF_GUID("5808C8AA-7E8F-42E0-85D2-E1E90434CFB3", N_("Microsoft LDM metadata")), + DEF_GUID("AF9B60A0-1431-4F62-BC68-3311714A69AD", N_("Microsoft LDM data")), + DEF_GUID("DE94BBA4-06D1-4D40-A16A-BFD50179D6AC", N_("Windows recovery environment")), + DEF_GUID("37AFFC90-EF7D-4E96-91C3-2D7AE055B174", N_("IBM General Parallel Fs")), + DEF_GUID("E75CAF8F-F680-4CEE-AFA3-B001E56EFC2D", N_("Microsoft Storage Spaces")), + + /* HP-UX */ + DEF_GUID("75894C1E-3AEB-11D3-B7C1-7B03A0000000", N_("HP-UX data")), + DEF_GUID("E2A1E728-32E3-11D6-A682-7B03A0000000", N_("HP-UX service")), + + /* Linux (http://www.freedesktop.org/wiki/Specifications/DiscoverablePartitionsSpec) */ + DEF_GUID("0657FD6D-A4AB-43C4-84E5-0933C84B4F4F", N_("Linux swap")), + DEF_GUID("0FC63DAF-8483-4772-8E79-3D69D8477DE4", N_("Linux filesystem")), + DEF_GUID("3B8F8425-20E0-4F3B-907F-1A25A76F98E8", N_("Linux server data")), + DEF_GUID("44479540-F297-41B2-9AF7-D131D5F0458A", N_("Linux root (x86)")), + DEF_GUID("4F68BCE3-E8CD-4DB1-96E7-FBCAF984B709", N_("Linux root (x86-64)")), + DEF_GUID("8DA63339-0007-60C0-C436-083AC8230908", N_("Linux reserved")), + DEF_GUID("933AC7E1-2EB4-4F13-B844-0E14E2AEF915", N_("Linux home")), + DEF_GUID("A19D880F-05FC-4D3B-A006-743F0F84911E", N_("Linux RAID")), + DEF_GUID("BC13C2FF-59E6-4262-A352-B275FD6F7172", N_("Linux extended boot")), + DEF_GUID("E6D6D379-F507-44C2-A23C-238F2A3DF928", N_("Linux LVM")), + + /* FreeBSD */ + DEF_GUID("516E7CB4-6ECF-11D6-8FF8-00022D09712B", N_("FreeBSD data")), + DEF_GUID("83BD6B9D-7F41-11DC-BE0B-001560B84F0F", N_("FreeBSD boot")), + DEF_GUID("516E7CB5-6ECF-11D6-8FF8-00022D09712B", N_("FreeBSD swap")), + DEF_GUID("516E7CB6-6ECF-11D6-8FF8-00022D09712B", N_("FreeBSD UFS")), + DEF_GUID("516E7CBA-6ECF-11D6-8FF8-00022D09712B", N_("FreeBSD ZFS")), + DEF_GUID("516E7CB8-6ECF-11D6-8FF8-00022D09712B", N_("FreeBSD Vinum")), + + /* Apple OSX */ + DEF_GUID("48465300-0000-11AA-AA11-00306543ECAC", N_("Apple HFS/HFS+")), + DEF_GUID("55465300-0000-11AA-AA11-00306543ECAC", N_("Apple UFS")), + DEF_GUID("52414944-0000-11AA-AA11-00306543ECAC", N_("Apple RAID")), + DEF_GUID("52414944-5F4F-11AA-AA11-00306543ECAC", N_("Apple RAID offline")), + DEF_GUID("426F6F74-0000-11AA-AA11-00306543ECAC", N_("Apple boot")), + DEF_GUID("4C616265-6C00-11AA-AA11-00306543ECAC", N_("Apple label")), + DEF_GUID("5265636F-7665-11AA-AA11-00306543ECAC", N_("Apple TV recovery")), + DEF_GUID("53746F72-6167-11AA-AA11-00306543ECAC", N_("Apple Core storage")), + + /* Solaris */ + DEF_GUID("6A82CB45-1DD2-11B2-99A6-080020736631", N_("Solaris boot")), + DEF_GUID("6A85CF4D-1DD2-11B2-99A6-080020736631", N_("Solaris root")), + /* same as Apple ZFS */ + DEF_GUID("6A898CC3-1DD2-11B2-99A6-080020736631", N_("Solaris /usr & Apple ZFS")), + DEF_GUID("6A87C46F-1DD2-11B2-99A6-080020736631", N_("Solaris swap")), + DEF_GUID("6A8B642B-1DD2-11B2-99A6-080020736631", N_("Solaris backup")), + DEF_GUID("6A8EF2E9-1DD2-11B2-99A6-080020736631", N_("Solaris /var")), + DEF_GUID("6A90BA39-1DD2-11B2-99A6-080020736631", N_("Solaris /home")), + DEF_GUID("6A9283A5-1DD2-11B2-99A6-080020736631", N_("Solaris alternate sector")), + DEF_GUID("6A945A3B-1DD2-11B2-99A6-080020736631", N_("Solaris reserved 1")), + DEF_GUID("6A9630D1-1DD2-11B2-99A6-080020736631", N_("Solaris reserved 2")), + DEF_GUID("6A980767-1DD2-11B2-99A6-080020736631", N_("Solaris reserved 3")), + DEF_GUID("6A96237F-1DD2-11B2-99A6-080020736631", N_("Solaris reserved 4")), + DEF_GUID("6A8D2AC7-1DD2-11B2-99A6-080020736631", N_("Solaris reserved 5")), + + /* NetBSD */ + DEF_GUID("49F48D32-B10E-11DC-B99B-0019D1879648", N_("NetBSD swap")), + DEF_GUID("49F48D5A-B10E-11DC-B99B-0019D1879648", N_("NetBSD FFS")), + DEF_GUID("49F48D82-B10E-11DC-B99B-0019D1879648", N_("NetBSD LFS")), + DEF_GUID("2DB519C4-B10E-11DC-B99B-0019D1879648", N_("NetBSD concatenated")), + DEF_GUID("2DB519EC-B10E-11DC-B99B-0019D1879648", N_("NetBSD encrypted")), + DEF_GUID("49F48DAA-B10E-11DC-B99B-0019D1879648", N_("NetBSD RAID")), + + /* ChromeOS */ + DEF_GUID("FE3A2A5D-4F32-41A7-B725-ACCC3285A309", N_("ChromeOS kernel")), + DEF_GUID("3CB8E202-3B7E-47DD-8A3C-7FF2A13CFCEC", N_("ChromeOS root fs")), + DEF_GUID("2E0A753D-9E48-43B0-8337-B15192CB1B5E", N_("ChromeOS reserved")), + + /* MidnightBSD */ + DEF_GUID("85D5E45A-237C-11E1-B4B3-E89A8F7FC3A7", N_("MidnightBSD data")), + DEF_GUID("85D5E45E-237C-11E1-B4B3-E89A8F7FC3A7", N_("MidnightBSD boot")), + DEF_GUID("85D5E45B-237C-11E1-B4B3-E89A8F7FC3A7", N_("MidnightBSD swap")), + DEF_GUID("0394Ef8B-237C-11E1-B4B3-E89A8F7FC3A7", N_("MidnightBSD UFS")), + DEF_GUID("85D5E45D-237C-11E1-B4B3-E89A8F7FC3A7", N_("MidnightBSD ZFS")), + DEF_GUID("85D5E45C-237C-11E1-B4B3-E89A8F7FC3A7", N_("MidnightBSD Vinum")), +}; + +/* gpt_entry macros */ +#define gpt_partition_start(_e) le64_to_cpu((_e)->lba_start) +#define gpt_partition_end(_e) le64_to_cpu((_e)->lba_end) + +/* + * in-memory fdisk GPT stuff + */ +struct fdisk_gpt_label { + struct fdisk_label head; /* generic part */ + + /* gpt specific part */ + struct gpt_header *pheader; /* primary header */ + struct gpt_header *bheader; /* backup header */ + struct gpt_entry *ents; /* entries (partitions) */ +}; + +static void gpt_deinit(struct fdisk_label *lb); + +static inline struct fdisk_gpt_label *self_label(struct fdisk_context *cxt) +{ + return (struct fdisk_gpt_label *) cxt->label; +} + +/* + * Returns the partition length, or 0 if end is before beginning. + */ +static uint64_t gpt_partition_size(const struct gpt_entry *e) +{ + uint64_t start = gpt_partition_start(e); + uint64_t end = gpt_partition_end(e); + + return start > end ? 0 : end - start + 1ULL; +} + +/* prints UUID in the real byte order! */ +static void gpt_debug_uuid(const char *mesg, struct gpt_guid *guid) +{ + const unsigned char *uuid = (unsigned char *) guid; + + fprintf(stderr, "%s: " + "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x\n", + mesg, + uuid[0], uuid[1], uuid[2], uuid[3], + uuid[4], uuid[5], + uuid[6], uuid[7], + uuid[8], uuid[9], + uuid[10], uuid[11], uuid[12], uuid[13], uuid[14],uuid[15]); +} + +/* + * UUID is traditionally 16 byte big-endian array, except Intel EFI + * specification where the UUID is a structure of little-endian fields. + */ +static void swap_efi_guid(struct gpt_guid *uid) +{ + uid->time_low = swab32(uid->time_low); + uid->time_mid = swab16(uid->time_mid); + uid->time_hi_and_version = swab16(uid->time_hi_and_version); +} + +static int string_to_guid(const char *in, struct gpt_guid *guid) +{ + if (uuid_parse(in, (unsigned char *) guid)) /* BE */ + return -1; + swap_efi_guid(guid); /* LE */ + return 0; +} + +static char *guid_to_string(const struct gpt_guid *guid, char *out) +{ + struct gpt_guid u = *guid; /* LE */ + + swap_efi_guid(&u); /* BE */ + uuid_unparse_upper((unsigned char *) &u, out); + + return out; +} + +static struct fdisk_parttype *gpt_partition_parttype( + struct fdisk_context *cxt, + const struct gpt_entry *e) +{ + struct fdisk_parttype *t; + char str[37]; + + guid_to_string(&e->type, str); + t = fdisk_label_get_parttype_from_string(cxt->label, str); + return t ? : fdisk_new_unknown_parttype(0, str); +} + +static void gpt_entry_set_type(struct gpt_entry *e, struct gpt_guid *uuid) +{ + e->type = *uuid; + DBG(LABEL, gpt_debug_uuid("new type", &(e->type))); +} + +static void gpt_entry_set_name(struct gpt_entry *e, char *str) +{ + char name[GPT_PART_NAME_LEN] = { 0 }; + size_t i, sz = strlen(str); + + if (sz) { + if (sz > GPT_PART_NAME_LEN) + sz = GPT_PART_NAME_LEN; + memcpy(name, str, sz); + } + + for (i = 0; i < GPT_PART_NAME_LEN; i++) + e->name[i] = cpu_to_le16((uint16_t) name[i]); +} + +static int gpt_entry_set_uuid(struct gpt_entry *e, char *str) +{ + struct gpt_guid uuid; + int rc; + + rc = string_to_guid(str, &uuid); + if (rc) + return rc; + + e->partition_guid = uuid; + return 0; +} + + +static const char *gpt_get_header_revstr(struct gpt_header *header) +{ + if (!header) + goto unknown; + + switch (header->revision) { + case GPT_HEADER_REVISION_V1_02: + return "1.2"; + case GPT_HEADER_REVISION_V1_00: + return "1.0"; + case GPT_HEADER_REVISION_V0_99: + return "0.99"; + default: + goto unknown; + } + +unknown: + return "unknown"; +} + +static inline int partition_unused(const struct gpt_entry *e) +{ + return !memcmp(&e->type, &GPT_UNUSED_ENTRY_GUID, + sizeof(struct gpt_guid)); +} + +/* + * Builds a clean new valid protective MBR - will wipe out any existing data. + * Returns 0 on success, otherwise < 0 on error. + */ +static int gpt_mknew_pmbr(struct fdisk_context *cxt) +{ + struct gpt_legacy_mbr *pmbr = NULL; + int rc; + + if (!cxt || !cxt->firstsector) + return -ENOSYS; + + rc = fdisk_init_firstsector_buffer(cxt); + if (rc) + return rc; + + pmbr = (struct gpt_legacy_mbr *) cxt->firstsector; + + pmbr->signature = cpu_to_le16(MSDOS_MBR_SIGNATURE); + pmbr->partition_record[0].os_type = EFI_PMBR_OSTYPE; + pmbr->partition_record[0].start_sector = 1; + pmbr->partition_record[0].end_head = 0xFE; + pmbr->partition_record[0].end_sector = 0xFF; + pmbr->partition_record[0].end_track = 0xFF; + pmbr->partition_record[0].starting_lba = cpu_to_le32(1); + pmbr->partition_record[0].size_in_lba = + cpu_to_le32(min((uint32_t) cxt->total_sectors - 1, 0xFFFFFFFF)); + + return 0; +} + +/* some universal differences between the headers */ +static void gpt_mknew_header_common(struct fdisk_context *cxt, + struct gpt_header *header, uint64_t lba) +{ + if (!cxt || !header) + return; + + header->my_lba = cpu_to_le64(lba); + + if (lba == GPT_PRIMARY_PARTITION_TABLE_LBA) { /* primary */ + header->alternative_lba = cpu_to_le64(cxt->total_sectors - 1); + header->partition_entry_lba = cpu_to_le64(2); + } else { /* backup */ + uint64_t esz = le32_to_cpu(header->npartition_entries) * sizeof(struct gpt_entry); + uint64_t esects = (esz + cxt->sector_size - 1) / cxt->sector_size; + + header->alternative_lba = cpu_to_le64(GPT_PRIMARY_PARTITION_TABLE_LBA); + header->partition_entry_lba = cpu_to_le64(cxt->total_sectors - 1 - esects); + } +} + +/* + * Builds a new GPT header (at sector lba) from a backup header2. + * If building a primary header, then backup is the secondary, and vice versa. + * + * Always pass a new (zeroized) header to build upon as we don't + * explicitly zero-set some values such as CRCs and reserved. + * + * Returns 0 on success, otherwise < 0 on error. + */ +static int gpt_mknew_header_from_bkp(struct fdisk_context *cxt, + struct gpt_header *header, + uint64_t lba, + struct gpt_header *header2) +{ + if (!cxt || !header || !header2) + return -ENOSYS; + + header->signature = header2->signature; + header->revision = header2->revision; + header->size = header2->size; + header->npartition_entries = header2->npartition_entries; + header->sizeof_partition_entry = header2->sizeof_partition_entry; + header->first_usable_lba = header2->first_usable_lba; + header->last_usable_lba = header2->last_usable_lba; + + memcpy(&header->disk_guid, + &header2->disk_guid, sizeof(header2->disk_guid)); + gpt_mknew_header_common(cxt, header, lba); + + return 0; +} + +static struct gpt_header *gpt_copy_header(struct fdisk_context *cxt, + struct gpt_header *src) +{ + struct gpt_header *res; + + if (!cxt || !src) + return NULL; + + res = calloc(1, sizeof(*res)); + if (!res) { + fdisk_warn(cxt, _("failed to allocate GPT header")); + return NULL; + } + + res->my_lba = src->alternative_lba; + res->alternative_lba = src->my_lba; + + res->signature = src->signature; + res->revision = src->revision; + res->size = src->size; + res->npartition_entries = src->npartition_entries; + res->sizeof_partition_entry = src->sizeof_partition_entry; + res->first_usable_lba = src->first_usable_lba; + res->last_usable_lba = src->last_usable_lba; + + memcpy(&res->disk_guid, &src->disk_guid, sizeof(src->disk_guid)); + + + if (res->my_lba == GPT_PRIMARY_PARTITION_TABLE_LBA) + res->partition_entry_lba = cpu_to_le64(2); + else { + uint64_t esz = le32_to_cpu(src->npartition_entries) * sizeof(struct gpt_entry); + uint64_t esects = (esz + cxt->sector_size - 1) / cxt->sector_size; + + res->partition_entry_lba = cpu_to_le64(cxt->total_sectors - 1 - esects); + } + + return res; +} + +static void count_first_last_lba(struct fdisk_context *cxt, + uint64_t *first, uint64_t *last) +{ + uint64_t esz = 0; + + assert(cxt); + + esz = sizeof(struct gpt_entry) * GPT_NPARTITIONS / cxt->sector_size; + *last = cxt->total_sectors - 2 - esz; + *first = esz + 2; + + if (*first < cxt->first_lba && cxt->first_lba < *last) + /* Align according to topology */ + *first = cxt->first_lba; +} + +/* + * Builds a clean new GPT header (currently under revision 1.0). + * + * Always pass a new (zeroized) header to build upon as we don't + * explicitly zero-set some values such as CRCs and reserved. + * + * Returns 0 on success, otherwise < 0 on error. + */ +static int gpt_mknew_header(struct fdisk_context *cxt, + struct gpt_header *header, uint64_t lba) +{ + uint64_t first, last; + int has_id = 0; + + if (!cxt || !header) + return -ENOSYS; + + header->signature = cpu_to_le64(GPT_HEADER_SIGNATURE); + header->revision = cpu_to_le32(GPT_HEADER_REVISION_V1_00); + header->size = cpu_to_le32(sizeof(struct gpt_header)); + + /* + * 128 partitions are the default. It can go beyond that, but + * we're creating a de facto header here, so no funny business. + */ + header->npartition_entries = cpu_to_le32(GPT_NPARTITIONS); + header->sizeof_partition_entry = cpu_to_le32(sizeof(struct gpt_entry)); + + count_first_last_lba(cxt, &first, &last); + header->first_usable_lba = cpu_to_le64(first); + header->last_usable_lba = cpu_to_le64(last); + + gpt_mknew_header_common(cxt, header, lba); + + if (cxt->script) { + const char *id = fdisk_script_get_header(cxt->script, "label-id"); + if (id && string_to_guid(id, &header->disk_guid) == 0) + has_id = 1; + } + + if (!has_id) { + uuid_generate_random((unsigned char *) &header->disk_guid); + swap_efi_guid(&header->disk_guid); + } + return 0; +} + +/* + * Checks if there is a valid protective MBR partition table. + * Returns 0 if it is invalid or failure. Otherwise, return + * GPT_MBR_PROTECTIVE or GPT_MBR_HYBRID, depeding on the detection. + */ +static int valid_pmbr(struct fdisk_context *cxt) +{ + int i, part = 0, ret = 0; /* invalid by default */ + struct gpt_legacy_mbr *pmbr = NULL; + uint32_t sz_lba = 0; + + if (!cxt->firstsector) + goto done; + + pmbr = (struct gpt_legacy_mbr *) cxt->firstsector; + + if (le16_to_cpu(pmbr->signature) != MSDOS_MBR_SIGNATURE) + goto done; + + /* LBA of the GPT partition header */ + if (pmbr->partition_record[0].starting_lba != + cpu_to_le32(GPT_PRIMARY_PARTITION_TABLE_LBA)) + goto done; + + /* seems like a valid MBR was found, check DOS primary partitions */ + for (i = 0; i < 4; i++) { + if (pmbr->partition_record[i].os_type == EFI_PMBR_OSTYPE) { + /* + * Ok, we at least know that there's a protective MBR, + * now check if there are other partition types for + * hybrid MBR. + */ + part = i; + ret = GPT_MBR_PROTECTIVE; + goto check_hybrid; + } + } + + if (ret != GPT_MBR_PROTECTIVE) + goto done; +check_hybrid: + for (i = 0 ; i < 4; i++) { + if ((pmbr->partition_record[i].os_type != EFI_PMBR_OSTYPE) && + (pmbr->partition_record[i].os_type != 0x00)) + ret = GPT_MBR_HYBRID; + } + + /* + * Protective MBRs take up the lesser of the whole disk + * or 2 TiB (32bit LBA), ignoring the rest of the disk. + * Some partitioning programs, nonetheless, choose to set + * the size to the maximum 32-bit limitation, disregarding + * the disk size. + * + * Hybrid MBRs do not necessarily comply with this. + * + * Consider a bad value here to be a warning to support dd-ing + * an image from a smaller disk to a bigger disk. + */ + if (ret == GPT_MBR_PROTECTIVE) { + sz_lba = le32_to_cpu(pmbr->partition_record[part].size_in_lba); + if (sz_lba != (uint32_t) cxt->total_sectors - 1 && sz_lba != 0xFFFFFFFF) { + fdisk_warnx(cxt, _("GPT PMBR size mismatch (%u != %u) " + "will be corrected by w(rite)."), + sz_lba, + (uint32_t) cxt->total_sectors - 1); + fdisk_label_set_changed(cxt->label, 1); + } + } +done: + return ret; +} + +static uint64_t last_lba(struct fdisk_context *cxt) +{ + struct stat s; + uint64_t sectors = 0; + + memset(&s, 0, sizeof(s)); + if (fstat(cxt->dev_fd, &s) == -1) { + fdisk_warn(cxt, _("gpt: stat() failed")); + return 0; + } + + if (S_ISBLK(s.st_mode)) + sectors = cxt->total_sectors - 1; + else if (S_ISREG(s.st_mode)) + sectors = ((uint64_t) s.st_size / + (uint64_t) cxt->sector_size) - 1ULL; + else + fdisk_warnx(cxt, _("gpt: cannot handle files with mode %o"), s.st_mode); + + DBG(LABEL, ul_debug("GPT last LBA: %ju", sectors)); + return sectors; +} + +static ssize_t read_lba(struct fdisk_context *cxt, uint64_t lba, + void *buffer, const size_t bytes) +{ + off_t offset = lba * cxt->sector_size; + + if (lseek(cxt->dev_fd, offset, SEEK_SET) == (off_t) -1) + return -1; + return read(cxt->dev_fd, buffer, bytes) != bytes; +} + + +/* Returns the GPT entry array */ +static struct gpt_entry *gpt_read_entries(struct fdisk_context *cxt, + struct gpt_header *header) +{ + ssize_t sz; + struct gpt_entry *ret = NULL; + off_t offset; + + assert(cxt); + assert(header); + + sz = le32_to_cpu(header->npartition_entries) * + le32_to_cpu(header->sizeof_partition_entry); + + ret = calloc(1, sz); + if (!ret) + return NULL; + offset = le64_to_cpu(header->partition_entry_lba) * + cxt->sector_size; + + if (offset != lseek(cxt->dev_fd, offset, SEEK_SET)) + goto fail; + if (sz != read(cxt->dev_fd, ret, sz)) + goto fail; + + return ret; + +fail: + free(ret); + return NULL; +} + +static inline uint32_t count_crc32(const unsigned char *buf, size_t len) +{ + return (crc32(~0L, buf, len) ^ ~0L); +} + +/* + * Recompute header and partition array 32bit CRC checksums. + * This function does not fail - if there's corruption, then it + * will be reported when checksuming it again (ie: probing or verify). + */ +static void gpt_recompute_crc(struct gpt_header *header, struct gpt_entry *ents) +{ + uint32_t crc = 0; + size_t entry_sz = 0; + + if (!header) + return; + + /* header CRC */ + header->crc32 = 0; + crc = count_crc32((unsigned char *) header, le32_to_cpu(header->size)); + header->crc32 = cpu_to_le32(crc); + + /* partition entry array CRC */ + header->partition_entry_array_crc32 = 0; + entry_sz = le32_to_cpu(header->npartition_entries) * + le32_to_cpu(header->sizeof_partition_entry); + + crc = count_crc32((unsigned char *) ents, entry_sz); + header->partition_entry_array_crc32 = cpu_to_le32(crc); +} + +/* + * Compute the 32bit CRC checksum of the partition table header. + * Returns 1 if it is valid, otherwise 0. + */ +static int gpt_check_header_crc(struct gpt_header *header, struct gpt_entry *ents) +{ + uint32_t crc, orgcrc = le32_to_cpu(header->crc32); + + header->crc32 = 0; + crc = count_crc32((unsigned char *) header, le32_to_cpu(header->size)); + header->crc32 = cpu_to_le32(orgcrc); + + if (crc == le32_to_cpu(header->crc32)) + return 1; + + /* + * If we have checksum mismatch it may be due to stale data, + * like a partition being added or deleted. Recompute the CRC again + * and make sure this is not the case. + */ + if (ents) { + gpt_recompute_crc(header, ents); + orgcrc = le32_to_cpu(header->crc32); + header->crc32 = 0; + crc = count_crc32((unsigned char *) header, le32_to_cpu(header->size)); + header->crc32 = cpu_to_le32(orgcrc); + + return crc == le32_to_cpu(header->crc32); + } + + return 0; +} + +/* + * It initializes the partition entry array. + * Returns 1 if the checksum is valid, otherwise 0. + */ +static int gpt_check_entryarr_crc(struct gpt_header *header, + struct gpt_entry *ents) +{ + int ret = 0; + ssize_t entry_sz; + uint32_t crc; + + if (!header || !ents) + goto done; + + entry_sz = le32_to_cpu(header->npartition_entries) * + le32_to_cpu(header->sizeof_partition_entry); + + if (!entry_sz) + goto done; + + crc = count_crc32((unsigned char *) ents, entry_sz); + ret = (crc == le32_to_cpu(header->partition_entry_array_crc32)); +done: + return ret; +} + +static int gpt_check_lba_sanity(struct fdisk_context *cxt, struct gpt_header *header) +{ + int ret = 0; + uint64_t lu, fu, lastlba = last_lba(cxt); + + fu = le64_to_cpu(header->first_usable_lba); + lu = le64_to_cpu(header->last_usable_lba); + + /* check if first and last usable LBA make sense */ + if (lu < fu) { + DBG(LABEL, ul_debug("error: header last LBA is before first LBA")); + goto done; + } + + /* check if first and last usable LBAs with the disk's last LBA */ + if (fu > lastlba || lu > lastlba) { + DBG(LABEL, ul_debug("error: header LBAs are after the disk's last LBA")); + goto done; + } + + /* the header has to be outside usable range */ + if (fu < GPT_PRIMARY_PARTITION_TABLE_LBA && + GPT_PRIMARY_PARTITION_TABLE_LBA < lu) { + DBG(LABEL, ul_debug("error: header outside of usable range")); + goto done; + } + + ret = 1; /* sane */ +done: + return ret; +} + +/* Check if there is a valid header signature */ +static int gpt_check_signature(struct gpt_header *header) +{ + return header->signature == cpu_to_le64(GPT_HEADER_SIGNATURE); +} + +/* + * Return the specified GPT Header, or NULL upon failure/invalid. + * Note that all tests must pass to ensure a valid header, + * we do not rely on only testing the signature for a valid probe. + */ +static struct gpt_header *gpt_read_header(struct fdisk_context *cxt, + uint64_t lba, + struct gpt_entry **_ents) +{ + struct gpt_header *header = NULL; + struct gpt_entry *ents = NULL; + uint32_t hsz; + + if (!cxt) + return NULL; + + header = calloc(1, sizeof(*header)); + if (!header) + return NULL; + + /* read and verify header */ + if (read_lba(cxt, lba, header, sizeof(struct gpt_header)) != 0) + goto invalid; + + if (!gpt_check_signature(header)) + goto invalid; + + if (!gpt_check_header_crc(header, NULL)) + goto invalid; + + /* read and verify entries */ + ents = gpt_read_entries(cxt, header); + if (!ents) + goto invalid; + + if (!gpt_check_entryarr_crc(header, ents)) + goto invalid; + + if (!gpt_check_lba_sanity(cxt, header)) + goto invalid; + + /* valid header must be at MyLBA */ + if (le64_to_cpu(header->my_lba) != lba) + goto invalid; + + /* make sure header size is between 92 and sector size bytes */ + hsz = le32_to_cpu(header->size); + if (hsz < GPT_HEADER_MINSZ || hsz > cxt->sector_size) + goto invalid; + + if (_ents) + *_ents = ents; + else + free(ents); + + DBG(LABEL, ul_debug("found valid GPT Header on LBA %ju", lba)); + return header; +invalid: + free(header); + free(ents); + + DBG(LABEL, ul_debug("read GPT Header on LBA %ju failed", lba)); + return NULL; +} + + +static int gpt_locate_disklabel(struct fdisk_context *cxt, int n, + const char **name, off_t *offset, size_t *size) +{ + struct fdisk_gpt_label *gpt; + + assert(cxt); + + *name = NULL; + *offset = 0; + *size = 0; + + switch (n) { + case 0: + *name = "PMBR"; + *offset = 0; + *size = 512; + break; + case 1: + *name = _("GPT Header"); + *offset = GPT_PRIMARY_PARTITION_TABLE_LBA * cxt->sector_size; + *size = sizeof(struct gpt_header); + break; + case 2: + *name = _("GPT Entries"); + gpt = self_label(cxt); + *offset = le64_to_cpu(gpt->pheader->partition_entry_lba) * cxt->sector_size; + *size = le32_to_cpu(gpt->pheader->npartition_entries) * + le32_to_cpu(gpt->pheader->sizeof_partition_entry); + break; + default: + return 1; /* no more chunks */ + } + + return 0; +} + + + +/* + * Returns the number of partitions that are in use. + */ +static unsigned partitions_in_use(struct gpt_header *header, + struct gpt_entry *ents) +{ + uint32_t i, used = 0; + + if (!header || ! ents) + return 0; + + for (i = 0; i < le32_to_cpu(header->npartition_entries); i++) + if (!partition_unused(&ents[i])) + used++; + return used; +} + + +/* + * Check if a partition is too big for the disk (sectors). + * Returns the faulting partition number, otherwise 0. + */ +static uint32_t check_too_big_partitions(struct gpt_header *header, + struct gpt_entry *ents, uint64_t sectors) +{ + uint32_t i; + + for (i = 0; i < le32_to_cpu(header->npartition_entries); i++) { + if (partition_unused(&ents[i])) + continue; + if (gpt_partition_end(&ents[i]) >= sectors) + return i + 1; + } + + return 0; +} + +/* + * Check if a partition ends before it begins + * Returns the faulting partition number, otherwise 0. + */ +static uint32_t check_start_after_end_paritions(struct gpt_header *header, + struct gpt_entry *ents) +{ + uint32_t i; + + for (i = 0; i < le32_to_cpu(header->npartition_entries); i++) { + if (partition_unused(&ents[i])) + continue; + if (gpt_partition_start(&ents[i]) > gpt_partition_end(&ents[i])) + return i + 1; + } + + return 0; +} + +/* + * Check if partition e1 overlaps with partition e2. + */ +static inline int partition_overlap(struct gpt_entry *e1, struct gpt_entry *e2) +{ + uint64_t start1 = gpt_partition_start(e1); + uint64_t end1 = gpt_partition_end(e1); + uint64_t start2 = gpt_partition_start(e2); + uint64_t end2 = gpt_partition_end(e2); + + return (start1 && start2 && (start1 <= end2) != (end1 < start2)); +} + +/* + * Find any partitions that overlap. + */ +static uint32_t check_overlap_partitions(struct gpt_header *header, + struct gpt_entry *ents) +{ + uint32_t i, j; + + for (i = 0; i < le32_to_cpu(header->npartition_entries); i++) + for (j = 0; j < i; j++) { + if (partition_unused(&ents[i]) || + partition_unused(&ents[j])) + continue; + if (partition_overlap(&ents[i], &ents[j])) { + DBG(LABEL, ul_debug("GPT partitions overlap detected [%u vs. %u]", i, j)); + return i + 1; + } + } + + return 0; +} + +/* + * Find the first available block after the starting point; returns 0 if + * there are no available blocks left, or error. From gdisk. + */ +static uint64_t find_first_available(struct gpt_header *header, + struct gpt_entry *ents, uint64_t start) +{ + uint64_t first; + uint32_t i, first_moved = 0; + + uint64_t fu, lu; + + if (!header || !ents) + return 0; + + fu = le64_to_cpu(header->first_usable_lba); + lu = le64_to_cpu(header->last_usable_lba); + + /* + * Begin from the specified starting point or from the first usable + * LBA, whichever is greater... + */ + first = start < fu ? fu : start; + + /* + * Now search through all partitions; if first is within an + * existing partition, move it to the next sector after that + * partition and repeat. If first was moved, set firstMoved + * flag; repeat until firstMoved is not set, so as to catch + * cases where partitions are out of sequential order.... + */ + do { + first_moved = 0; + for (i = 0; i < le32_to_cpu(header->npartition_entries); i++) { + if (partition_unused(&ents[i])) + continue; + if (first < gpt_partition_start(&ents[i])) + continue; + if (first <= gpt_partition_end(&ents[i])) { + first = gpt_partition_end(&ents[i]) + 1; + first_moved = 1; + } + } + } while (first_moved == 1); + + if (first > lu) + first = 0; + + return first; +} + + +/* Returns last available sector in the free space pointed to by start. From gdisk. */ +static uint64_t find_last_free(struct gpt_header *header, + struct gpt_entry *ents, uint64_t start) +{ + uint32_t i; + uint64_t nearest_start; + + if (!header || !ents) + return 0; + + nearest_start = le64_to_cpu(header->last_usable_lba); + + for (i = 0; i < le32_to_cpu(header->npartition_entries); i++) { + uint64_t ps = gpt_partition_start(&ents[i]); + + if (nearest_start > ps && ps > start) + nearest_start = ps - 1; + } + + return nearest_start; +} + +/* Returns the last free sector on the disk. From gdisk. */ +static uint64_t find_last_free_sector(struct gpt_header *header, + struct gpt_entry *ents) +{ + uint32_t i, last_moved; + uint64_t last = 0; + + if (!header || !ents) + goto done; + + /* start by assuming the last usable LBA is available */ + last = le64_to_cpu(header->last_usable_lba); + do { + last_moved = 0; + for (i = 0; i < le32_to_cpu(header->npartition_entries); i++) { + if ((last >= gpt_partition_start(&ents[i])) && + (last <= gpt_partition_end(&ents[i]))) { + last = gpt_partition_start(&ents[i]) - 1; + last_moved = 1; + } + } + } while (last_moved == 1); +done: + return last; +} + +/* + * Finds the first available sector in the largest block of unallocated + * space on the disk. Returns 0 if there are no available blocks left. + * From gdisk. + */ +static uint64_t find_first_in_largest(struct gpt_header *header, + struct gpt_entry *ents) +{ + uint64_t start = 0, first_sect, last_sect; + uint64_t segment_size, selected_size = 0, selected_segment = 0; + + if (!header || !ents) + goto done; + + do { + first_sect = find_first_available(header, ents, start); + if (first_sect != 0) { + last_sect = find_last_free(header, ents, first_sect); + segment_size = last_sect - first_sect + 1; + + if (segment_size > selected_size) { + selected_size = segment_size; + selected_segment = first_sect; + } + start = last_sect + 1; + } + } while (first_sect != 0); + +done: + return selected_segment; +} + +/* + * Find the total number of free sectors, the number of segments in which + * they reside, and the size of the largest of those segments. From gdisk. + */ +static uint64_t get_free_sectors(struct fdisk_context *cxt, struct gpt_header *header, + struct gpt_entry *ents, uint32_t *nsegments, + uint64_t *largest_segment) +{ + uint32_t num = 0; + uint64_t first_sect, last_sect; + uint64_t largest_seg = 0, segment_sz; + uint64_t totfound = 0, start = 0; /* starting point for each search */ + + if (!cxt->total_sectors) + goto done; + + do { + first_sect = find_first_available(header, ents, start); + if (first_sect) { + last_sect = find_last_free(header, ents, first_sect); + segment_sz = last_sect - first_sect + 1; + + if (segment_sz > largest_seg) + largest_seg = segment_sz; + totfound += segment_sz; + num++; + start = last_sect + 1; + } + } while (first_sect); + +done: + if (nsegments) + *nsegments = num; + if (largest_segment) + *largest_segment = largest_seg; + + return totfound; +} + +static int gpt_probe_label(struct fdisk_context *cxt) +{ + int mbr_type; + struct fdisk_gpt_label *gpt; + + assert(cxt); + assert(cxt->label); + assert(fdisk_is_label(cxt, GPT)); + + gpt = self_label(cxt); + + /* TODO: it would be nice to support scenario when GPT headers are OK, + * but PMBR is corrupt */ + mbr_type = valid_pmbr(cxt); + if (!mbr_type) + goto failed; + + DBG(LABEL, ul_debug("found a %s MBR", mbr_type == GPT_MBR_PROTECTIVE ? + "protective" : "hybrid")); + + /* primary header */ + gpt->pheader = gpt_read_header(cxt, GPT_PRIMARY_PARTITION_TABLE_LBA, + &gpt->ents); + + if (gpt->pheader) + /* primary OK, try backup from alternative LBA */ + gpt->bheader = gpt_read_header(cxt, + le64_to_cpu(gpt->pheader->alternative_lba), + NULL); + else + /* primary corrupted -- try last LBA */ + gpt->bheader = gpt_read_header(cxt, last_lba(cxt), &gpt->ents); + + if (!gpt->pheader && !gpt->bheader) + goto failed; + + /* primary OK, backup corrupted -- recovery */ + if (gpt->pheader && !gpt->bheader) { + fdisk_warnx(cxt, _("The backup GPT table is corrupt, but the " + "primary appears OK, so that will be used.")); + gpt->bheader = gpt_copy_header(cxt, gpt->pheader); + if (!gpt->bheader) + goto failed; + gpt_recompute_crc(gpt->bheader, gpt->ents); + + /* primary corrupted, backup OK -- recovery */ + } else if (!gpt->pheader && gpt->bheader) { + fdisk_warnx(cxt, _("The primary GPT table is corrupt, but the " + "backup appears OK, so that will be used.")); + gpt->pheader = gpt_copy_header(cxt, gpt->bheader); + if (!gpt->pheader) + goto failed; + gpt_recompute_crc(gpt->pheader, gpt->ents); + } + + cxt->label->nparts_max = le32_to_cpu(gpt->pheader->npartition_entries); + cxt->label->nparts_cur = partitions_in_use(gpt->pheader, gpt->ents); + return 1; +failed: + DBG(LABEL, ul_debug("GPT probe failed")); + gpt_deinit(cxt->label); + return 0; +} + +/* + * Stolen from libblkid - can be removed once partition semantics + * are added to the fdisk API. + */ +static char *encode_to_utf8(unsigned char *src, size_t count) +{ + uint16_t c; + char *dest; + size_t i, j, len = count; + + dest = calloc(1, count); + if (!dest) + return NULL; + + for (j = i = 0; i + 2 <= count; i += 2) { + /* always little endian */ + c = (src[i+1] << 8) | src[i]; + if (c == 0) { + dest[j] = '\0'; + break; + } else if (c < 0x80) { + if (j+1 >= len) + break; + dest[j++] = (uint8_t) c; + } else if (c < 0x800) { + if (j+2 >= len) + break; + dest[j++] = (uint8_t) (0xc0 | (c >> 6)); + dest[j++] = (uint8_t) (0x80 | (c & 0x3f)); + } else { + if (j+3 >= len) + break; + dest[j++] = (uint8_t) (0xe0 | (c >> 12)); + dest[j++] = (uint8_t) (0x80 | ((c >> 6) & 0x3f)); + dest[j++] = (uint8_t) (0x80 | (c & 0x3f)); + } + } + dest[j] = '\0'; + + return dest; +} + +static int gpt_entry_attrs_to_string(struct gpt_entry *e, char **res) +{ + unsigned int n, count = 0; + size_t l; + char *bits, *p; + uint64_t attrs; + + assert(e); + assert(res); + + *res = NULL; + attrs = le64_to_cpu(e->attrs); + if (!attrs) + return 0; /* no attributes at all */ + + bits = (char *) &attrs; + + /* Note that sizeof() is correct here, we need separators between + * the strings so also count \0 is correct */ + *res = calloc(1, sizeof(GPT_ATTRSTR_NOBLOCK) + + sizeof(GPT_ATTRSTR_REQ) + + sizeof(GPT_ATTRSTR_LEGACY) + + sizeof("GUID:") + (GPT_ATTRBIT_GUID_COUNT * 3)); + if (!*res) + return -errno; + + p = *res; + if (isset(bits, GPT_ATTRBIT_REQ)) { + memcpy(p, GPT_ATTRSTR_REQ, (l = sizeof(GPT_ATTRSTR_REQ))); + p += l - 1; + } + if (isset(bits, GPT_ATTRBIT_NOBLOCK)) { + if (p > *res) + *p++ = ' '; + memcpy(p, GPT_ATTRSTR_NOBLOCK, (l = sizeof(GPT_ATTRSTR_NOBLOCK))); + p += l - 1; + } + if (isset(bits, GPT_ATTRBIT_LEGACY)) { + if (p > *res) + *p++ = ' '; + memcpy(p, GPT_ATTRSTR_LEGACY, (l = sizeof(GPT_ATTRSTR_LEGACY))); + p += l - 1; + } + + for (n = GPT_ATTRBIT_GUID_FIRST; + n < GPT_ATTRBIT_GUID_FIRST + GPT_ATTRBIT_GUID_COUNT; n++) { + + if (!isset(bits, n)) + continue; + if (!count) { + if (p > *res) + *p++ = ' '; + p += sprintf(p, "GUID:%u", n); + } else + p += sprintf(p, ",%u", n); + count++; + } + + return 0; +} + +static int gpt_entry_attrs_from_string( + struct fdisk_context *cxt, + struct gpt_entry *e, + const char *str) +{ + const char *p = str; + uint64_t attrs = 0; + char *bits; + + assert(e); + assert(p); + + DBG(LABEL, ul_debug("GPT: parsing string attributes '%s'", p)); + + bits = (char *) &attrs; + + while (p && *p) { + int bit = -1; + + while (isblank(*p)) p++; + if (!*p) + break; + + DBG(LABEL, ul_debug(" parsing item '%s'", p)); + + if (strncmp(p, "GUID:", 5) == 0) { + p += 5; + continue; + } else if (strncmp(p, GPT_ATTRSTR_REQ, + sizeof(GPT_ATTRSTR_REQ) - 1) == 0) { + bit = GPT_ATTRBIT_REQ; + p += sizeof(GPT_ATTRSTR_REQ) - 1; + } else if (strncmp(p, GPT_ATTRSTR_LEGACY, + sizeof(GPT_ATTRSTR_LEGACY) - 1) == 0) { + bit = GPT_ATTRBIT_LEGACY; + p += sizeof(GPT_ATTRSTR_LEGACY) - 1; + } else if (strncmp(p, GPT_ATTRSTR_NOBLOCK, + sizeof(GPT_ATTRSTR_NOBLOCK) - 1) == 0) { + bit = GPT_ATTRBIT_NOBLOCK; + p += sizeof(GPT_ATTRSTR_NOBLOCK) - 1; + } else if (isdigit((unsigned int) *p)) { + char *end = NULL; + + errno = 0; + bit = strtol(p, &end, 0); + if (errno || !end || end == str + || bit < GPT_ATTRBIT_GUID_FIRST + || bit >= GPT_ATTRBIT_GUID_FIRST + GPT_ATTRBIT_GUID_COUNT) + bit = -1; + else + p = end; + } + + if (bit < 0) { + fdisk_warnx(cxt, _("unssuported GPT attribute bit '%s'"), p); + return -EINVAL; + } + + setbit(bits, bit); + + while (isblank(*p)) p++; + if (*p == ',') + p++; + } + + e->attrs = cpu_to_le64(attrs); + return 0; +} + +static int gpt_get_partition(struct fdisk_context *cxt, size_t n, + struct fdisk_partition *pa) +{ + struct fdisk_gpt_label *gpt; + struct gpt_entry *e; + char u_str[37]; + int rc = 0; + + assert(cxt); + assert(cxt->label); + assert(fdisk_is_label(cxt, GPT)); + + gpt = self_label(cxt); + + if ((uint32_t) n >= le32_to_cpu(gpt->pheader->npartition_entries)) + return -EINVAL; + + gpt = self_label(cxt); + e = &gpt->ents[n]; + + pa->used = !partition_unused(e) || gpt_partition_start(e); + if (!pa->used) + return 0; + + pa->start = gpt_partition_start(e); + pa->size = gpt_partition_size(e); + pa->type = gpt_partition_parttype(cxt, e); + + if (guid_to_string(&e->partition_guid, u_str)) { + pa->uuid = strdup(u_str); + if (!pa->uuid) { + rc = -errno; + goto done; + } + } else + pa->uuid = NULL; + + rc = gpt_entry_attrs_to_string(e, &pa->attrs); + if (rc) + goto done; + + pa->name = encode_to_utf8((unsigned char *)e->name, sizeof(e->name)); + return 0; +done: + fdisk_reset_partition(pa); + return rc; +} + + +static int gpt_set_partition(struct fdisk_context *cxt, size_t n, + struct fdisk_partition *pa) +{ + struct fdisk_gpt_label *gpt; + struct gpt_entry *e; + int rc = 0; + uint64_t start, end; + + assert(cxt); + assert(cxt->label); + assert(fdisk_is_label(cxt, GPT)); + + gpt = self_label(cxt); + + if ((uint32_t) n >= le32_to_cpu(gpt->pheader->npartition_entries)) + return -EINVAL; + + FDISK_INIT_UNDEF(start); + FDISK_INIT_UNDEF(end); + + gpt = self_label(cxt); + e = &gpt->ents[n]; + + if (pa->uuid) { + char new_u[37], old_u[37]; + + guid_to_string(&e->partition_guid, old_u); + rc = gpt_entry_set_uuid(e, pa->uuid); + if (rc) + return rc; + guid_to_string(&e->partition_guid, new_u); + fdisk_info(cxt, _("Partition UUID changed from %s to %s."), + old_u, new_u); + } + + if (pa->name) { + char *old = encode_to_utf8((unsigned char *)e->name, sizeof(e->name)); + gpt_entry_set_name(e, pa->name); + + fdisk_info(cxt, _("Partition name changed from '%s' to '%.*s'."), + old, (int) GPT_PART_NAME_LEN, pa->name); + free(old); + } + + if (pa->type && pa->type->typestr) { + struct gpt_guid typeid; + + rc = string_to_guid(pa->type->typestr, &typeid); + if (rc) + return rc; + gpt_entry_set_type(e, &typeid); + } + if (pa->attrs) { + rc = gpt_entry_attrs_from_string(cxt, e, pa->attrs); + if (rc) + return rc; + } + + if (fdisk_partition_has_start(pa)) + start = pa->start; + if (fdisk_partition_has_size(pa)) + end = gpt_partition_start(e) + pa->size - 1ULL; + + if (pa->end_follow_default) { + /* enlarge */ + if (!FDISK_IS_UNDEF(start)) + start = gpt_partition_start(e); + end = find_last_free(gpt->bheader, gpt->ents, start); + if (!end) + FDISK_INIT_UNDEF(end); + } + + if (!FDISK_IS_UNDEF(start)) + e->lba_start = cpu_to_le64(start); + if (!FDISK_IS_UNDEF(end)) + e->lba_end = cpu_to_le64(end); + + gpt_recompute_crc(gpt->pheader, gpt->ents); + gpt_recompute_crc(gpt->bheader, gpt->ents); + + fdisk_label_set_changed(cxt->label, 1); + return rc; +} + + +/* + * List label partitions. + */ +static int gpt_list_disklabel(struct fdisk_context *cxt) +{ + assert(cxt); + assert(cxt->label); + assert(fdisk_is_label(cxt, GPT)); + + if (fdisk_is_details(cxt)) { + struct gpt_header *h = self_label(cxt)->pheader; + + fdisk_info(cxt, _("First LBA: %ju"), h->first_usable_lba); + fdisk_info(cxt, _("Last LBA: %ju"), h->last_usable_lba); + /* TRANSLATORS: The LBA (Logical Block Address) of the backup GPT header. */ + fdisk_info(cxt, _("Alternative LBA: %ju"), h->alternative_lba); + /* TRANSLATORS: The start of the array of partition entries. */ + fdisk_info(cxt, _("Partition entries LBA: %ju"), h->partition_entry_lba); + fdisk_info(cxt, _("Allocated partition entries: %u"), h->npartition_entries); + } + + return 0; +} + +/* + * Write partitions. + * Returns 0 on success, or corresponding error otherwise. + */ +static int gpt_write_partitions(struct fdisk_context *cxt, + struct gpt_header *header, struct gpt_entry *ents) +{ + off_t offset = le64_to_cpu(header->partition_entry_lba) * cxt->sector_size; + uint32_t nparts = le32_to_cpu(header->npartition_entries); + uint32_t totwrite = nparts * le32_to_cpu(header->sizeof_partition_entry); + ssize_t rc; + + if (offset != lseek(cxt->dev_fd, offset, SEEK_SET)) + goto fail; + + rc = write(cxt->dev_fd, ents, totwrite); + if (rc > 0 && totwrite == (uint32_t) rc) + return 0; +fail: + return -errno; +} + +/* + * Write a GPT header to a specified LBA + * Returns 0 on success, or corresponding error otherwise. + */ +static int gpt_write_header(struct fdisk_context *cxt, + struct gpt_header *header, uint64_t lba) +{ + off_t offset = lba * cxt->sector_size; + + if (offset != lseek(cxt->dev_fd, offset, SEEK_SET)) + goto fail; + if (cxt->sector_size == + (size_t) write(cxt->dev_fd, header, cxt->sector_size)) + return 0; +fail: + return -errno; +} + +/* + * Write the protective MBR. + * Returns 0 on success, or corresponding error otherwise. + */ +static int gpt_write_pmbr(struct fdisk_context *cxt) +{ + off_t offset; + struct gpt_legacy_mbr *pmbr = NULL; + + assert(cxt); + assert(cxt->firstsector); + + pmbr = (struct gpt_legacy_mbr *) cxt->firstsector; + + /* zero out the legacy partitions */ + memset(pmbr->partition_record, 0, sizeof(pmbr->partition_record)); + + pmbr->signature = cpu_to_le16(MSDOS_MBR_SIGNATURE); + pmbr->partition_record[0].os_type = EFI_PMBR_OSTYPE; + pmbr->partition_record[0].start_sector = 1; + pmbr->partition_record[0].end_head = 0xFE; + pmbr->partition_record[0].end_sector = 0xFF; + pmbr->partition_record[0].end_track = 0xFF; + pmbr->partition_record[0].starting_lba = cpu_to_le32(1); + + /* + * Set size_in_lba to the size of the disk minus one. If the size of the disk + * is too large to be represented by a 32bit LBA (2Tb), set it to 0xFFFFFFFF. + */ + if (cxt->total_sectors - 1 > 0xFFFFFFFFULL) + pmbr->partition_record[0].size_in_lba = cpu_to_le32(0xFFFFFFFF); + else + pmbr->partition_record[0].size_in_lba = + cpu_to_le32(cxt->total_sectors - 1UL); + + offset = GPT_PMBR_LBA * cxt->sector_size; + if (offset != lseek(cxt->dev_fd, offset, SEEK_SET)) + goto fail; + + /* pMBR covers the first sector (LBA) of the disk */ + if (write_all(cxt->dev_fd, pmbr, cxt->sector_size)) + goto fail; + return 0; +fail: + return -errno; +} + +/* + * Writes in-memory GPT and pMBR data to disk. + * Returns 0 if successful write, otherwise, a corresponding error. + * Any indication of error will abort the operation. + */ +static int gpt_write_disklabel(struct fdisk_context *cxt) +{ + struct fdisk_gpt_label *gpt; + int mbr_type; + + assert(cxt); + assert(cxt->label); + assert(fdisk_is_label(cxt, GPT)); + + gpt = self_label(cxt); + mbr_type = valid_pmbr(cxt); + + /* check that disk is big enough to handle the backup header */ + if (le64_to_cpu(gpt->pheader->alternative_lba) > cxt->total_sectors) + goto err0; + + /* check that the backup header is properly placed */ + if (le64_to_cpu(gpt->pheader->alternative_lba) < cxt->total_sectors - 1) + /* TODO: correct this (with user authorization) and write */ + goto err0; + + if (check_overlap_partitions(gpt->pheader, gpt->ents)) + goto err0; + + /* recompute CRCs for both headers */ + gpt_recompute_crc(gpt->pheader, gpt->ents); + gpt_recompute_crc(gpt->bheader, gpt->ents); + + /* + * UEFI requires writing in this specific order: + * 1) backup partition tables + * 2) backup GPT header + * 3) primary partition tables + * 4) primary GPT header + * 5) protective MBR + * + * If any write fails, we abort the rest. + */ + if (gpt_write_partitions(cxt, gpt->bheader, gpt->ents) != 0) + goto err1; + if (gpt_write_header(cxt, gpt->bheader, + le64_to_cpu(gpt->pheader->alternative_lba)) != 0) + goto err1; + if (gpt_write_partitions(cxt, gpt->pheader, gpt->ents) != 0) + goto err1; + if (gpt_write_header(cxt, gpt->pheader, GPT_PRIMARY_PARTITION_TABLE_LBA) != 0) + goto err1; + + if (mbr_type == GPT_MBR_HYBRID) + fdisk_warnx(cxt, _("The device contains hybrid MBR -- writing GPT only. " + "You have to sync the MBR manually.")); + else if (gpt_write_pmbr(cxt) != 0) + goto err1; + + DBG(LABEL, ul_debug("GPT write success")); + return 0; +err0: + DBG(LABEL, ul_debug("GPT write failed: incorrect input")); + errno = EINVAL; + return -EINVAL; +err1: + DBG(LABEL, ul_debug("GPT write failed: %m")); + return -errno; +} + +/* + * Verify data integrity and report any found problems for: + * - primary and backup header validations + * - paritition validations + */ +static int gpt_verify_disklabel(struct fdisk_context *cxt) +{ + int nerror = 0; + unsigned int ptnum; + struct fdisk_gpt_label *gpt; + + assert(cxt); + assert(cxt->label); + assert(fdisk_is_label(cxt, GPT)); + + gpt = self_label(cxt); + + if (!gpt || !gpt->bheader) { + nerror++; + fdisk_warnx(cxt, _("Disk does not contain a valid backup header.")); + } + + if (!gpt_check_header_crc(gpt->pheader, gpt->ents)) { + nerror++; + fdisk_warnx(cxt, _("Invalid primary header CRC checksum.")); + } + if (gpt->bheader && !gpt_check_header_crc(gpt->bheader, gpt->ents)) { + nerror++; + fdisk_warnx(cxt, _("Invalid backup header CRC checksum.")); + } + + if (!gpt_check_entryarr_crc(gpt->pheader, gpt->ents)) { + nerror++; + fdisk_warnx(cxt, _("Invalid partition entry checksum.")); + } + + if (!gpt_check_lba_sanity(cxt, gpt->pheader)) { + nerror++; + fdisk_warnx(cxt, _("Invalid primary header LBA sanity checks.")); + } + if (gpt->bheader && !gpt_check_lba_sanity(cxt, gpt->bheader)) { + nerror++; + fdisk_warnx(cxt, _("Invalid backup header LBA sanity checks.")); + } + + if (le64_to_cpu(gpt->pheader->my_lba) != GPT_PRIMARY_PARTITION_TABLE_LBA) { + nerror++; + fdisk_warnx(cxt, _("MyLBA mismatch with real position at primary header.")); + } + if (gpt->bheader && le64_to_cpu(gpt->bheader->my_lba) != last_lba(cxt)) { + nerror++; + fdisk_warnx(cxt, _("MyLBA mismatch with real position at backup header.")); + + } + if (le64_to_cpu(gpt->pheader->alternative_lba) >= cxt->total_sectors) { + nerror++; + fdisk_warnx(cxt, _("Disk is too small to hold all data.")); + } + + /* + * if the GPT is the primary table, check the alternateLBA + * to see if it is a valid GPT + */ + if (gpt->bheader && (le64_to_cpu(gpt->pheader->my_lba) != + le64_to_cpu(gpt->bheader->alternative_lba))) { + nerror++; + fdisk_warnx(cxt, _("Primary and backup header mismatch.")); + } + + ptnum = check_overlap_partitions(gpt->pheader, gpt->ents); + if (ptnum) { + nerror++; + fdisk_warnx(cxt, _("Partition %u overlaps with partition %u."), + ptnum, ptnum+1); + } + + ptnum = check_too_big_partitions(gpt->pheader, gpt->ents, cxt->total_sectors); + if (ptnum) { + nerror++; + fdisk_warnx(cxt, _("Partition %u is too big for the disk."), + ptnum); + } + + ptnum = check_start_after_end_paritions(gpt->pheader, gpt->ents); + if (ptnum) { + nerror++; + fdisk_warnx(cxt, _("Partition %u ends before it starts."), + ptnum); + } + + if (!nerror) { /* yay :-) */ + uint32_t nsegments = 0; + uint64_t free_sectors = 0, largest_segment = 0; + char *strsz = NULL; + + fdisk_info(cxt, _("No errors detected.")); + fdisk_info(cxt, _("Header version: %s"), gpt_get_header_revstr(gpt->pheader)); + fdisk_info(cxt, _("Using %u out of %d partitions."), + partitions_in_use(gpt->pheader, gpt->ents), + le32_to_cpu(gpt->pheader->npartition_entries)); + + free_sectors = get_free_sectors(cxt, gpt->pheader, gpt->ents, + &nsegments, &largest_segment); + if (largest_segment) + strsz = size_to_human_string(SIZE_SUFFIX_SPACE | SIZE_SUFFIX_3LETTER, + largest_segment * cxt->sector_size); + + fdisk_info(cxt, + P_("A total of %ju free sectors is available in %u segment.", + "A total of %ju free sectors is available in %u segments " + "(the largest is %s).", nsegments), + free_sectors, nsegments, strsz); + free(strsz); + + } else + fdisk_warnx(cxt, + P_("%d error detected.", "%d errors detected.", nerror), + nerror); + + return 0; +} + +/* Delete a single GPT partition, specified by partnum. */ +static int gpt_delete_partition(struct fdisk_context *cxt, + size_t partnum) +{ + struct fdisk_gpt_label *gpt; + + assert(cxt); + assert(cxt->label); + assert(fdisk_is_label(cxt, GPT)); + + gpt = self_label(cxt); + + if (partnum >= cxt->label->nparts_max + || partition_unused(&gpt->ents[partnum])) + return -EINVAL; + + /* hasta la vista, baby! */ + memset(&gpt->ents[partnum], 0, sizeof(struct gpt_entry)); + if (!partition_unused(&gpt->ents[partnum])) + return -EINVAL; + else { + gpt_recompute_crc(gpt->pheader, gpt->ents); + gpt_recompute_crc(gpt->bheader, gpt->ents); + cxt->label->nparts_cur--; + fdisk_label_set_changed(cxt->label, 1); + } + + return 0; +} + + +/* Performs logical checks to add a new partition entry */ +static int gpt_add_partition( + struct fdisk_context *cxt, + struct fdisk_partition *pa, + size_t *partno) +{ + uint64_t user_f, user_l; /* user input ranges for first and last sectors */ + uint64_t disk_f, disk_l; /* first and last available sector ranges on device*/ + uint64_t dflt_f, dflt_l; /* largest segment (default) */ + struct gpt_guid typeid; + struct fdisk_gpt_label *gpt; + struct gpt_header *pheader; + struct gpt_entry *e, *ents; + struct fdisk_ask *ask = NULL; + size_t partnum; + int rc; + + assert(cxt); + assert(cxt->label); + assert(fdisk_is_label(cxt, GPT)); + + gpt = self_label(cxt); + pheader = gpt->pheader; + ents = gpt->ents; + + rc = fdisk_partition_next_partno(pa, cxt, &partnum); + if (rc) { + DBG(LABEL, ul_debug("GPT failed to get next partno")); + return rc; + } + if (!partition_unused(&ents[partnum])) { + fdisk_warnx(cxt, _("Partition %zu is already defined. " + "Delete it before re-adding it."), partnum +1); + return -ERANGE; + } + if (le32_to_cpu(pheader->npartition_entries) == + partitions_in_use(pheader, ents)) { + fdisk_warnx(cxt, _("All partitions are already in use.")); + return -ENOSPC; + } + if (!get_free_sectors(cxt, pheader, ents, NULL, NULL)) { + fdisk_warnx(cxt, _("No free sectors available.")); + return -ENOSPC; + } + + string_to_guid(pa && pa->type && pa->type->typestr ? + pa->type->typestr: + GPT_DEFAULT_ENTRY_TYPE, &typeid); + + disk_f = find_first_available(pheader, ents, pheader->first_usable_lba); + + /* if first sector no explicitly defined then ignore small gaps before + * the first partition */ + if ((!pa || !fdisk_partition_has_start(pa)) + && !partition_unused(&ents[0]) + && disk_f < gpt_partition_start(&ents[0])) { + + do { + uint64_t x; + DBG(LABEL, ul_debug("testing first sector %ju", disk_f)); + disk_f = find_first_available(pheader, ents, disk_f); + if (!disk_f) + break; + x = find_last_free(pheader, ents, disk_f); + if (x - disk_f >= cxt->grain / cxt->sector_size) + break; + DBG(LABEL, ul_debug("first sector %ju addresses to small space, continue...", disk_f)); + disk_f = x + 1; + } while(1); + + if (disk_f == 0) + disk_f = find_first_available(pheader, ents, pheader->first_usable_lba); + } + + disk_l = find_last_free_sector(pheader, ents); + + /* the default is the largest free space */ + dflt_f = find_first_in_largest(pheader, ents); + dflt_l = find_last_free(pheader, ents, dflt_f); + + /* align the default in range <dflt_f,dflt_l>*/ + dflt_f = fdisk_align_lba_in_range(cxt, dflt_f, dflt_f, dflt_l); + + /* first sector */ + if (pa && pa->start_follow_default) { + user_f = dflt_f; + + } else if (pa && fdisk_partition_has_start(pa)) { + DBG(LABEL, ul_debug("first sector defined: %ju", pa->start)); + if (pa->start != find_first_available(pheader, ents, pa->start)) { + fdisk_warnx(cxt, _("Sector %ju already used."), pa->start); + return -ERANGE; + } + user_f = pa->start; + } else { + /* ask by dialog */ + for (;;) { + if (!ask) + ask = fdisk_new_ask(); + else + fdisk_reset_ask(ask); + + /* First sector */ + fdisk_ask_set_query(ask, _("First sector")); + fdisk_ask_set_type(ask, FDISK_ASKTYPE_NUMBER); + fdisk_ask_number_set_low(ask, disk_f); /* minimal */ + fdisk_ask_number_set_default(ask, dflt_f); /* default */ + fdisk_ask_number_set_high(ask, disk_l); /* maximal */ + + rc = fdisk_do_ask(cxt, ask); + if (rc) + goto done; + + user_f = fdisk_ask_number_get_result(ask); + if (user_f != find_first_available(pheader, ents, user_f)) { + fdisk_warnx(cxt, _("Sector %ju already used."), user_f); + continue; + } + break; + } + } + + + /* Last sector */ + dflt_l = find_last_free(pheader, ents, user_f); + + if (pa && pa->end_follow_default) { + user_l = dflt_l; + + } else if (pa && fdisk_partition_has_size(pa)) { + user_l = user_f + pa->size - 1; + DBG(LABEL, ul_debug("size defined: %ju, end: %ju (last possible: %ju)", + pa->size, user_l, dflt_l)); + if (user_l != dflt_l && !pa->size_explicit) + user_l = fdisk_align_lba_in_range(cxt, user_l, user_f, dflt_l) - 1; + + } else { + for (;;) { + if (!ask) + ask = fdisk_new_ask(); + else + fdisk_reset_ask(ask); + + fdisk_ask_set_query(ask, _("Last sector, +sectors or +size{K,M,G,T,P}")); + fdisk_ask_set_type(ask, FDISK_ASKTYPE_OFFSET); + fdisk_ask_number_set_low(ask, user_f); /* minimal */ + fdisk_ask_number_set_default(ask, dflt_l); /* default */ + fdisk_ask_number_set_high(ask, dflt_l); /* maximal */ + fdisk_ask_number_set_base(ask, user_f); /* base for relative input */ + fdisk_ask_number_set_unit(ask, cxt->sector_size); + + rc = fdisk_do_ask(cxt, ask); + if (rc) + goto done; + + user_l = fdisk_ask_number_get_result(ask); + if (fdisk_ask_number_is_relative(ask)) { + user_l = fdisk_align_lba_in_range(cxt, user_l, user_f, dflt_l) - 1; + + /* no space for anything useful, use all space + if (user_l + (cxt->grain / cxt->sector_size) > dflt_l) + user_l = dflt_l; + */ + } + + if (user_l > user_f && user_l <= disk_l) + break; + } + } + + + if (user_f > user_l || partnum >= cxt->label->nparts_max) { + fdisk_warnx(cxt, _("Could not create partition %zu"), partnum + 1); + rc = -EINVAL; + goto done; + } + + assert(!FDISK_IS_UNDEF(user_l)); + assert(!FDISK_IS_UNDEF(user_f)); + + e = &ents[partnum]; + e->lba_end = cpu_to_le64(user_l); + e->lba_start = cpu_to_le64(user_f); + + gpt_entry_set_type(e, &typeid); + + if (pa && pa->uuid) { + /* Sometimes it's necessary to create a copy of the PT and + * reuse already defined UUID + */ + rc = gpt_entry_set_uuid(e, pa->uuid); + if (rc) + goto done; + } else { + /* Any time a new partition entry is created a new GUID must be + * generated for that partition, and every partition is guaranteed + * to have a unique GUID. + */ + uuid_generate_random((unsigned char *) &e->partition_guid); + swap_efi_guid(&e->partition_guid); + } + + if (pa && pa->name && *pa->name) + gpt_entry_set_name(e, pa->name); + if (pa && pa->attrs) + gpt_entry_attrs_from_string(cxt, e, pa->attrs); + + DBG(LABEL, ul_debug("GPT new partition: partno=%zu, start=%ju, end=%ju, size=%ju", + partnum, + gpt_partition_start(e), + gpt_partition_end(e), + gpt_partition_size(e))); + + gpt_recompute_crc(gpt->pheader, ents); + gpt_recompute_crc(gpt->bheader, ents); + + /* report result */ + { + struct fdisk_parttype *t; + + cxt->label->nparts_cur++; + fdisk_label_set_changed(cxt->label, 1); + + t = gpt_partition_parttype(cxt, &ents[partnum]); + fdisk_info_new_partition(cxt, partnum + 1, user_f, user_l, t); + fdisk_unref_parttype(t); + } + + rc = 0; + if (partno) + *partno = partnum; +done: + fdisk_unref_ask(ask); + return rc; +} + +/* + * Create a new GPT disklabel - destroys any previous data. + */ +static int gpt_create_disklabel(struct fdisk_context *cxt) +{ + int rc = 0; + ssize_t esz = 0; + char str[37]; + struct fdisk_gpt_label *gpt; + + assert(cxt); + assert(cxt->label); + assert(fdisk_is_label(cxt, GPT)); + + gpt = self_label(cxt); + + /* label private stuff has to be empty, see gpt_deinit() */ + assert(gpt->pheader == NULL); + assert(gpt->bheader == NULL); + + /* + * When no header, entries or pmbr is set, we're probably + * dealing with a new, empty disk - so always allocate memory + * to deal with the data structures whatever the case is. + */ + rc = gpt_mknew_pmbr(cxt); + if (rc < 0) + goto done; + + /* primary */ + gpt->pheader = calloc(1, sizeof(*gpt->pheader)); + if (!gpt->pheader) { + rc = -ENOMEM; + goto done; + } + rc = gpt_mknew_header(cxt, gpt->pheader, GPT_PRIMARY_PARTITION_TABLE_LBA); + if (rc < 0) + goto done; + + /* backup ("copy" primary) */ + gpt->bheader = calloc(1, sizeof(*gpt->bheader)); + if (!gpt->bheader) { + rc = -ENOMEM; + goto done; + } + rc = gpt_mknew_header_from_bkp(cxt, gpt->bheader, + last_lba(cxt), gpt->pheader); + if (rc < 0) + goto done; + + esz = le32_to_cpu(gpt->pheader->npartition_entries) * + le32_to_cpu(gpt->pheader->sizeof_partition_entry); + gpt->ents = calloc(1, esz); + if (!gpt->ents) { + rc = -ENOMEM; + goto done; + } + gpt_recompute_crc(gpt->pheader, gpt->ents); + gpt_recompute_crc(gpt->bheader, gpt->ents); + + cxt->label->nparts_max = le32_to_cpu(gpt->pheader->npartition_entries); + cxt->label->nparts_cur = 0; + + guid_to_string(&gpt->pheader->disk_guid, str); + fdisk_label_set_changed(cxt->label, 1); + fdisk_info(cxt, _("Created a new GPT disklabel (GUID: %s)."), str); +done: + return rc; +} + +static int gpt_get_disklabel_id(struct fdisk_context *cxt, char **id) +{ + struct fdisk_gpt_label *gpt; + char str[37]; + + assert(cxt); + assert(id); + assert(cxt->label); + assert(fdisk_is_label(cxt, GPT)); + + gpt = self_label(cxt); + guid_to_string(&gpt->pheader->disk_guid, str); + + *id = strdup(str); + if (!*id) + return -ENOMEM; + return 0; +} + +static int gpt_set_disklabel_id(struct fdisk_context *cxt) +{ + struct fdisk_gpt_label *gpt; + struct gpt_guid uuid; + char *str, *old, *new; + int rc; + + assert(cxt); + assert(cxt->label); + assert(fdisk_is_label(cxt, GPT)); + + gpt = self_label(cxt); + if (fdisk_ask_string(cxt, + _("Enter new disk UUID (in 8-4-4-4-12 format)"), &str)) + return -EINVAL; + + rc = string_to_guid(str, &uuid); + free(str); + + if (rc) { + fdisk_warnx(cxt, _("Failed to parse your UUID.")); + return rc; + } + + gpt_get_disklabel_id(cxt, &old); + + gpt->pheader->disk_guid = uuid; + gpt->bheader->disk_guid = uuid; + + gpt_recompute_crc(gpt->pheader, gpt->ents); + gpt_recompute_crc(gpt->bheader, gpt->ents); + + gpt_get_disklabel_id(cxt, &new); + + fdisk_info(cxt, _("Disk identifier changed from %s to %s."), old, new); + + free(old); + free(new); + fdisk_label_set_changed(cxt->label, 1); + return 0; +} + +static int gpt_part_is_used(struct fdisk_context *cxt, size_t i) +{ + struct fdisk_gpt_label *gpt; + struct gpt_entry *e; + + assert(cxt); + assert(cxt->label); + assert(fdisk_is_label(cxt, GPT)); + + gpt = self_label(cxt); + + if ((uint32_t) i >= le32_to_cpu(gpt->pheader->npartition_entries)) + return 0; + e = &gpt->ents[i]; + + return !partition_unused(e) || gpt_partition_start(e); +} + +/** + * fdisk_gpt_is_hybrid: + * @cxt: context + * + * The regular GPT contains PMBR (dummy protective MBR) where the protective + * MBR does not address any partitions. + * + * Hybrid GPT contains regular MBR where this partition table addresses the + * same partitions as GPT. It's recommended to not use hybrid GPT due to MBR + * limits. + * + * The libfdisk does not provide functionality to sync GPT and MBR, you have to + * directly access and modify (P)MBR (see fdisk_new_nested_context()). + * + * Returns: 1 if partition table detected as hybrid otherwise return 0 + */ +int fdisk_gpt_is_hybrid(struct fdisk_context *cxt) +{ + assert(cxt); + return valid_pmbr(cxt) == GPT_MBR_HYBRID; +} + +static int gpt_toggle_partition_flag( + struct fdisk_context *cxt, + size_t i, + unsigned long flag) +{ + struct fdisk_gpt_label *gpt; + uint64_t attrs, tmp; + char *bits; + const char *name = NULL; + int bit = -1, rc; + + assert(cxt); + assert(cxt->label); + assert(fdisk_is_label(cxt, GPT)); + + DBG(LABEL, ul_debug("GPT entry attribute change requested partno=%zu", i)); + gpt = self_label(cxt); + + if ((uint32_t) i >= le32_to_cpu(gpt->pheader->npartition_entries)) + return -EINVAL; + + attrs = le64_to_cpu(gpt->ents[i].attrs); + bits = (char *) &attrs; + + switch (flag) { + case GPT_FLAG_REQUIRED: + bit = GPT_ATTRBIT_REQ; + name = GPT_ATTRSTR_REQ; + break; + case GPT_FLAG_NOBLOCK: + bit = GPT_ATTRBIT_NOBLOCK; + name = GPT_ATTRSTR_NOBLOCK; + break; + case GPT_FLAG_LEGACYBOOT: + bit = GPT_ATTRBIT_LEGACY; + name = GPT_ATTRSTR_LEGACY; + break; + case GPT_FLAG_GUIDSPECIFIC: + rc = fdisk_ask_number(cxt, 48, 48, 63, _("Enter GUID specific bit"), &tmp); + if (rc) + return rc; + bit = tmp; + break; + default: + /* already specified PT_FLAG_GUIDSPECIFIC bit */ + if (flag >= 48 && flag <= 63) { + bit = flag; + flag = GPT_FLAG_GUIDSPECIFIC; + } + break; + } + + if (bit < 0) { + fdisk_warnx(cxt, _("failed to toggle unsupported bit %lu"), flag); + return -EINVAL; + } + + if (!isset(bits, bit)) + setbit(bits, bit); + else + clrbit(bits, bit); + + gpt->ents[i].attrs = cpu_to_le64(attrs); + + if (flag == GPT_FLAG_GUIDSPECIFIC) + fdisk_info(cxt, isset(bits, bit) ? + _("The GUID specific bit %d on partition %zu is enabled now.") : + _("The GUID specific bit %d on partition %zu is disabled now."), + bit, i + 1); + else + fdisk_info(cxt, isset(bits, bit) ? + _("The %s flag on partition %zu is enabled now.") : + _("The %s flag on partition %zu is disabled now."), + name, i + 1); + + gpt_recompute_crc(gpt->pheader, gpt->ents); + gpt_recompute_crc(gpt->bheader, gpt->ents); + fdisk_label_set_changed(cxt->label, 1); + return 0; +} + +static int gpt_entry_cmp_start(const void *a, const void *b) +{ + struct gpt_entry *ae = (struct gpt_entry *) a, + *be = (struct gpt_entry *) b; + int au = partition_unused(ae), + bu = partition_unused(be); + + if (au && bu) + return 0; + if (au) + return 1; + if (bu) + return -1; + + return cmp_numbers(gpt_partition_start(ae), gpt_partition_start(be)); +} + +/* sort partition by start sector */ +static int gpt_reorder(struct fdisk_context *cxt) +{ + struct fdisk_gpt_label *gpt; + size_t nparts; + + assert(cxt); + assert(cxt->label); + assert(fdisk_is_label(cxt, GPT)); + + gpt = self_label(cxt); + nparts = le32_to_cpu(gpt->pheader->npartition_entries); + + qsort(gpt->ents, nparts, sizeof(struct gpt_entry), + gpt_entry_cmp_start); + + gpt_recompute_crc(gpt->pheader, gpt->ents); + gpt_recompute_crc(gpt->bheader, gpt->ents); + fdisk_label_set_changed(cxt->label, 1); + + fdisk_info(cxt, _("Done.")); + return 0; +} + +static int gpt_reset_alignment(struct fdisk_context *cxt) +{ + struct fdisk_gpt_label *gpt; + struct gpt_header *h; + + assert(cxt); + assert(cxt->label); + assert(fdisk_is_label(cxt, GPT)); + + gpt = self_label(cxt); + h = gpt ? gpt->pheader : NULL; + + if (h) { + /* always follow existing table */ + cxt->first_lba = h->first_usable_lba; + cxt->last_lba = h->last_usable_lba; + } else { + /* estimate ranges for GPT */ + uint64_t first, last; + + count_first_last_lba(cxt, &first, &last); + + if (cxt->first_lba < first) + cxt->first_lba = first; + if (cxt->last_lba > last) + cxt->last_lba = last; + } + + return 0; +} +/* + * Deinitialize fdisk-specific variables + */ +static void gpt_deinit(struct fdisk_label *lb) +{ + struct fdisk_gpt_label *gpt = (struct fdisk_gpt_label *) lb; + + if (!gpt) + return; + + free(gpt->ents); + free(gpt->pheader); + free(gpt->bheader); + + gpt->ents = NULL; + gpt->pheader = NULL; + gpt->bheader = NULL; +} + +static const struct fdisk_label_operations gpt_operations = +{ + .probe = gpt_probe_label, + .write = gpt_write_disklabel, + .verify = gpt_verify_disklabel, + .create = gpt_create_disklabel, + .list = gpt_list_disklabel, + .locate = gpt_locate_disklabel, + .reorder = gpt_reorder, + .get_id = gpt_get_disklabel_id, + .set_id = gpt_set_disklabel_id, + + .get_part = gpt_get_partition, + .set_part = gpt_set_partition, + .add_part = gpt_add_partition, + .del_part = gpt_delete_partition, + + .part_is_used = gpt_part_is_used, + .part_toggle_flag = gpt_toggle_partition_flag, + + .deinit = gpt_deinit, + + .reset_alignment = gpt_reset_alignment +}; + +static const struct fdisk_field gpt_fields[] = +{ + /* basic */ + { FDISK_FIELD_DEVICE, N_("Device"), 10, 0 }, + { FDISK_FIELD_START, N_("Start"), 5, FDISK_FIELDFL_NUMBER }, + { FDISK_FIELD_END, N_("End"), 5, FDISK_FIELDFL_NUMBER }, + { FDISK_FIELD_SECTORS, N_("Sectors"), 5, FDISK_FIELDFL_NUMBER }, + { FDISK_FIELD_SIZE, N_("Size"), 5, FDISK_FIELDFL_NUMBER | FDISK_FIELDFL_EYECANDY }, + { FDISK_FIELD_TYPE, N_("Type"), 0.1, FDISK_FIELDFL_EYECANDY }, + /* expert */ + { FDISK_FIELD_TYPEID, N_("Type-UUID"), 36, FDISK_FIELDFL_DETAIL }, + { FDISK_FIELD_UUID, N_("UUID"), 36, FDISK_FIELDFL_DETAIL }, + { FDISK_FIELD_NAME, N_("Name"), 0.2, FDISK_FIELDFL_DETAIL }, + { FDISK_FIELD_ATTR, N_("Attrs"), 0, FDISK_FIELDFL_DETAIL } +}; + +/* + * allocates GPT in-memory stuff + */ +struct fdisk_label *fdisk_new_gpt_label(struct fdisk_context *cxt) +{ + struct fdisk_label *lb; + struct fdisk_gpt_label *gpt; + + assert(cxt); + + gpt = calloc(1, sizeof(*gpt)); + if (!gpt) + return NULL; + + /* initialize generic part of the driver */ + lb = (struct fdisk_label *) gpt; + lb->name = "gpt"; + lb->id = FDISK_DISKLABEL_GPT; + lb->op = &gpt_operations; + lb->parttypes = gpt_parttypes; + lb->nparttypes = ARRAY_SIZE(gpt_parttypes); + + lb->fields = gpt_fields; + lb->nfields = ARRAY_SIZE(gpt_fields); + + return lb; +} diff --git a/libblkid/libfdisk/src/init.c b/libblkid/libfdisk/src/init.c new file mode 100644 index 000000000..61acb0a4f --- /dev/null +++ b/libblkid/libfdisk/src/init.c @@ -0,0 +1,55 @@ + +#include "fdiskP.h" + + +/** + * SECTION: init + * @title: Library initialization + * @short_description: initialize debug stuff + * + */ + +UL_DEBUG_DEFINE_MASK(libfdisk); +UL_DEBUG_DEFINE_MASKNAMES(libfdisk) = +{ + { "all", LIBFDISK_DEBUG_ALL, "info about all subsystems" }, + { "ask", LIBFDISK_DEBUG_ASK, "fdisk dialogs" }, + { "help", LIBFDISK_DEBUG_HELP, "this help" }, + { "cxt", LIBFDISK_DEBUG_CXT, "library context (handler)" }, + { "label", LIBFDISK_DEBUG_LABEL, "disk label utils" }, + { "part", LIBFDISK_DEBUG_PART, "partition utils" }, + { "parttype", LIBFDISK_DEBUG_PARTTYPE,"partition type utils" }, + { "script", LIBFDISK_DEBUG_SCRIPT, "sfdisk-like scripts" }, + { "tab", LIBFDISK_DEBUG_TAB, "table utils"}, + { NULL, 0 } +}; + +/** + * fdisk_init_debug: + * @mask: debug mask (0xffff to enable full debuging) + * + * If the @mask is not specified then this function reads + * LIBFDISK_DEBUG environment variable to get the mask. + * + * Already initialized debugging stuff cannot be changed. It does not + * have effect to call this function twice. + * + * It's strongly recommended to use fdisk_init_debug(0) in your code. + */ +void fdisk_init_debug(int mask) +{ + if (libfdisk_debug_mask) + return; + + __UL_INIT_DEBUG(libfdisk, LIBFDISK_DEBUG_, mask, LIBFDISK_DEBUG); + + + if (libfdisk_debug_mask != LIBFDISK_DEBUG_INIT + && libfdisk_debug_mask != (LIBFDISK_DEBUG_HELP|LIBFDISK_DEBUG_INIT)) { + + DBG(INIT, ul_debug("library debug mask: 0x%04x", libfdisk_debug_mask)); + } + + ON_DBG(HELP, ul_debug_print_masks("LIBFDISK_DEBUG", + UL_DEBUG_MASKNAMES(libfdisk))); +} diff --git a/libblkid/libfdisk/src/iter.c b/libblkid/libfdisk/src/iter.c new file mode 100644 index 000000000..9a0b0801c --- /dev/null +++ b/libblkid/libfdisk/src/iter.c @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2009 Karel Zak <kzak@redhat.com> + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ + +/** + * SECTION: iter + * @title: Iterator + * @short_description: unified iterator + * + * The iterator keeps the direction and the last position for access to the + * internal library tables/lists. + * + * It's very unusual to use the same iterator on multiple places in your + * application or share the same iterator, for this purpose libfdisk does not + * provide reference counting for this object. It's recommended to initialize + * the iterator by fdisk_new_iter() at begin of your function and then + * fdisk_free_iter() before you return from the function. + * + * Don't forget to call fdisk_reset_iter() if you want to use the iterator more + * than once. + */ +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +#include "fdiskP.h" + +/** + * fdisk_new_iter: + * @direction: FDISK_INTER_{FOR,BACK}WARD direction + * + * Returns: newly allocated generic libmount iterator. + */ +struct fdisk_iter *fdisk_new_iter(int direction) +{ + struct fdisk_iter *itr = calloc(1, sizeof(*itr)); + if (!itr) + return NULL; + itr->direction = direction; + return itr; +} + +/** + * fdisk_free_iter: + * @itr: iterator pointer + * + * Deallocates the iterator. + */ +void fdisk_free_iter(struct fdisk_iter *itr) +{ + free(itr); +} + +/** + * fdisk_reset_iter: + * @itr: iterator pointer + * @direction: FDISK_INTER_{FOR,BACK}WARD or -1 to keep the direction unchanged + * + * Resets the iterator. + */ +void fdisk_reset_iter(struct fdisk_iter *itr, int direction) +{ + if (direction == -1) + direction = itr->direction; + + memset(itr, 0, sizeof(*itr)); + itr->direction = direction; +} + +/** + * fdisk_iter_get_direction: + * @itr: iterator pointer + * + * Returns: FDISK_INTER_{FOR,BACK}WARD + */ +int fdisk_iter_get_direction(struct fdisk_iter *itr) +{ + return itr->direction; +} diff --git a/libblkid/libfdisk/src/label.c b/libblkid/libfdisk/src/label.c new file mode 100644 index 000000000..750cfca55 --- /dev/null +++ b/libblkid/libfdisk/src/label.c @@ -0,0 +1,569 @@ + +#include "fdiskP.h" + + +/** + * SECTION: label + * @title: Label + * @short_description: disk label (PT) specific data and functions + * + * The fdisk_new_context() initializes all label drivers, and allocate + * per-label specific data struct. This concept allows to store label specific + * settings to the label driver independently on the currently active label + * driver. Note that label struct cannot be deallocated, so there is no + * reference counting for fdisk_label objects. All is destroyed by + * fdisk_unref_context() only. + * + * Anyway, all label drives share in-memory first sector. The function + * fdisk_create_disklabel() overwrites the sector. But it's possible that + * label driver also uses another buffers, for example GPT uses more than only + * the first sector. + * + * All label operations are in-memory only, except fdisk_write_disklabel(). + * + * All functions that use "struct fdisk_context" rather than "struct + * fdisk_label" use the currently active label driver. + */ + + +int fdisk_probe_labels(struct fdisk_context *cxt) +{ + size_t i; + + cxt->label = NULL; + + for (i = 0; i < cxt->nlabels; i++) { + struct fdisk_label *lb = cxt->labels[i]; + struct fdisk_label *org = fdisk_get_label(cxt, NULL); + int rc; + + if (!lb->op->probe) + continue; + if (lb->disabled) { + DBG(CXT, ul_debugobj(cxt, "%s: disabled -- ignore", lb->name)); + continue; + } + DBG(CXT, ul_debugobj(cxt, "probing for %s", lb->name)); + + cxt->label = lb; + rc = lb->op->probe(cxt); + cxt->label = org; + + if (rc != 1) { + if (lb->op->deinit) + lb->op->deinit(lb); /* for sure */ + continue; + } + + __fdisk_switch_label(cxt, lb); + return 0; + } + + DBG(CXT, ul_debugobj(cxt, "no label found")); + return 1; /* not found */ +} + +/** + * fdisk_label_get_name: + * @lb: label + * + * Returns: label name + */ +const char *fdisk_label_get_name(const struct fdisk_label *lb) +{ + return lb ? lb->name : NULL; +} + +/** + * fdisk_label_is_labeltype: + * @lb: label + * + * Returns: FDISK_DISKLABEL_*. + */ +int fdisk_label_get_type(const struct fdisk_label *lb) +{ + return lb->id; +} + +/** + * fdisk_label_require_geometry: + * @lb: label + * + * Returns: 1 if label requires CHS geometry + */ +int fdisk_label_require_geometry(const struct fdisk_label *lb) +{ + assert(lb); + + return lb->flags & FDISK_LABEL_FL_REQUIRE_GEOMETRY ? 1 : 0; +} + +/** + * fdisk_label_get_fields_ids + * @lb: label (or NULL for the current label) + * @cxt: context + * @ids: returns allocated array with FDISK_FIELD_* IDs + * @nids: returns number of items in fields + * + * This function returns the default fields for the label. + * + * Note that the set of the default fields depends on fdisk_enable_details() + * function. If the details are enabled then this function usually returns more + * fields. + * + * Returns: 0 on success, otherwise, a corresponding error. + */ +int fdisk_label_get_fields_ids( + const struct fdisk_label *lb, + struct fdisk_context *cxt, + int **ids, size_t *nids) +{ + size_t i, n; + int *c; + + assert(cxt); + + if (!lb) + lb = cxt->label; + if (!lb) + return -EINVAL; + if (!lb->fields || !lb->nfields) + return -ENOSYS; + c = calloc(lb->nfields, sizeof(int)); + if (!c) + return -ENOMEM; + for (n = 0, i = 0; i < lb->nfields; i++) { + int id = lb->fields[i].id; + + if ((fdisk_is_details(cxt) && + (lb->fields[i].flags & FDISK_FIELDFL_EYECANDY)) + || (!fdisk_is_details(cxt) && + (lb->fields[i].flags & FDISK_FIELDFL_DETAIL)) + || (id == FDISK_FIELD_SECTORS && + fdisk_use_cylinders(cxt)) + || (id == FDISK_FIELD_CYLINDERS && + !fdisk_use_cylinders(cxt))) + continue; + + c[n++] = id; + } + if (ids) + *ids = c; + else + free(c); + if (nids) + *nids = n; + return 0; +} + +/** + * fdisk_label_get_field: + * @lb: label + * @id: FDISK_FIELD_* + * + * The field struct describes data stored in struct fdisk_partition. The info + * about data is usable for example to generate human readable output (e.g. + * fdisk 'p'rint command). See fdisk_partition_to_stirng() and fdisk code. + * + * Returns: pointer to static instance of the field. + */ +const struct fdisk_field *fdisk_label_get_field(const struct fdisk_label *lb, int id) +{ + size_t i; + + assert(lb); + assert(id > 0); + + for (i = 0; i < lb->nfields; i++) { + if (lb->fields[i].id == id) + return &lb->fields[i]; + } + + return NULL; +} + +/** + * fdisk_label_get_field_by_name + * @lb: label + * @name: field name + * + * Returns: pointer to static instance of the field. + */ +const struct fdisk_field *fdisk_label_get_field_by_name( + const struct fdisk_label *lb, + const char *name) +{ + size_t i; + + assert(lb); + assert(name); + + for (i = 0; i < lb->nfields; i++) { + if (lb->fields[i].name && strcasecmp(lb->fields[i].name, name) == 0) + return &lb->fields[i]; + } + + return NULL; +} + + +/** + * fdisk_field_get_id: + * @field: field instance + * + * Returns: field Id (FDISK_FIELD_*) + */ +int fdisk_field_get_id(const struct fdisk_field *field) +{ + return field ? field->id : -EINVAL; +} + +/** + * fdisk_field_get_name: + * @field: field instance + * + * Returns: field name + */ +const char *fdisk_field_get_name(const struct fdisk_field *field) +{ + return field ? field->name : NULL; +} + +/** + * fdisk_field_get_width: + * @field: field instance + * + * Returns: libsmartcols compatible width. + */ +double fdisk_field_get_width(const struct fdisk_field *field) +{ + return field ? field->width : -EINVAL; +} + +/** + * fdisk_field_is_number: + * @field: field instance + * + * Returns: 1 if field represent number + */ +int fdisk_field_is_number(const struct fdisk_field *field) +{ + return field->flags ? field->flags & FDISK_FIELDFL_NUMBER : 0; +} + + +/** + * fdisk_write_disklabel: + * @cxt: fdisk context + * + * Write in-memory changes to disk. Be careful! + * + * Returns: 0 on success, otherwise, a corresponding error. + */ +int fdisk_write_disklabel(struct fdisk_context *cxt) +{ + if (!cxt || !cxt->label || cxt->readonly) + return -EINVAL; + if (!cxt->label->op->write) + return -ENOSYS; + return cxt->label->op->write(cxt); +} + +/** + * fdisk_verify_disklabel: + * @cxt: fdisk context + * + * Verifies the partition table. + * + * Returns: 0 on success, otherwise, a corresponding error. + */ +int fdisk_verify_disklabel(struct fdisk_context *cxt) +{ + if (!cxt || !cxt->label) + return -EINVAL; + if (!cxt->label->op->verify) + return -ENOSYS; + if (fdisk_missing_geometry(cxt)) + return -EINVAL; + + return cxt->label->op->verify(cxt); +} + +/** + * fdisk_list_disklabel: + * @cxt: fdisk context + * + * Lists details about disklabel, but no partitions. + * + * This function uses libfdisk ASK interface to print data. The details about + * partitions table are printed by FDISK_ASKTYPE_INFO. + * + * Returns: 0 on success, otherwise, a corresponding error. + */ +int fdisk_list_disklabel(struct fdisk_context *cxt) +{ + if (!cxt || !cxt->label) + return -EINVAL; + if (!cxt->label->op->list) + return -ENOSYS; + + return cxt->label->op->list(cxt); +} + +/** + * fdisk_create_disklabel: + * @cxt: fdisk context + * @name: label name + * + * Creates a new disk label of type @name. If @name is NULL, then it will + * create a default system label type, either SUN or DOS. The function + * automaticaly switches the current label driver to @name. The function + * fdisk_get_label() returns the current label driver. + * + * The function modifies in-memory data only. + * + * Returns: 0 on success, otherwise, a corresponding error. + */ +int fdisk_create_disklabel(struct fdisk_context *cxt, const char *name) +{ + int haslabel = 0; + struct fdisk_label *lb; + + if (!cxt) + return -EINVAL; + + if (!name) { /* use default label creation */ +#ifdef __sparc__ + name = "sun"; +#else + name = "dos"; +#endif + } + + if (cxt->label) { + fdisk_deinit_label(cxt->label); + haslabel = 1; + } + + lb = fdisk_get_label(cxt, name); + if (!lb || lb->disabled) + return -EINVAL; + if (!lb->op->create) + return -ENOSYS; + + __fdisk_switch_label(cxt, lb); + + if (haslabel && !cxt->parent) + fdisk_reset_device_properties(cxt); + + DBG(CXT, ul_debugobj(cxt, "create a new %s label", lb->name)); + return cxt->label->op->create(cxt); +} + +/** + * fdisk_locate_disklabel: + * @cxt: context + * @n: N item + * @name: return item name + * @offset: return offset where is item + * @size: of the item + * + * Locate disklabel and returns info about @n item of the label. For example + * GPT is composed from two items, PMBR and GPT, n=0 return offset to PMBR and n=1 + * return offset to GPT. For more details see 'D' expect fdisk command. + * + * Returns: 0 on succes, <0 on error, 1 no more items. + */ +int fdisk_locate_disklabel(struct fdisk_context *cxt, int n, const char **name, + off_t *offset, size_t *size) +{ + if (!cxt || !cxt->label) + return -EINVAL; + if (!cxt->label->op->locate) + return -ENOSYS; + + DBG(CXT, ul_debugobj(cxt, "locating %d chunk of %s.", n, cxt->label->name)); + return cxt->label->op->locate(cxt, n, name, offset, size); +} + + +/** + * fdisk_get_disklabel_id: + * @cxt: fdisk context + * @id: returns pointer to allocated string (MBR Id or GPT dirk UUID) + * + * Returns: 0 on success, otherwise, a corresponding error. + */ +int fdisk_get_disklabel_id(struct fdisk_context *cxt, char **id) +{ + if (!cxt || !cxt->label) + return -EINVAL; + if (!cxt->label->op->get_id) + return -ENOSYS; + + DBG(CXT, ul_debugobj(cxt, "asking for disk %s ID", cxt->label->name)); + return cxt->label->op->get_id(cxt, id); +} + +/** + * fdisk_set_disklabel_id: + * @cxt: fdisk context + * + * Returns: 0 on success, otherwise, a corresponding error. + */ +int fdisk_set_disklabel_id(struct fdisk_context *cxt) +{ + if (!cxt || !cxt->label) + return -EINVAL; + if (!cxt->label->op->set_id) + return -ENOSYS; + + DBG(CXT, ul_debugobj(cxt, "setting %s disk ID", cxt->label->name)); + return cxt->label->op->set_id(cxt); +} + +/** + * fdisk_set_partition_type: + * @cxt: fdisk context + * @partnum: partition number + * @t: new type + * + * Returns: 0 on success, < 0 on error. + */ +int fdisk_set_partition_type(struct fdisk_context *cxt, + size_t partnum, + struct fdisk_parttype *t) +{ + if (!cxt || !cxt->label || !t) + return -EINVAL; + + + if (cxt->label->op->set_part) { + struct fdisk_partition *pa = fdisk_new_partition(); + int rc; + + if (!pa) + return -ENOMEM; + fdisk_partition_set_type(pa, t); + + DBG(CXT, ul_debugobj(cxt, "partition: %zd: set type", partnum)); + rc = cxt->label->op->set_part(cxt, partnum, pa); + fdisk_unref_partition(pa); + return rc; + } + + return -ENOSYS; +} + + +/** + * fdisk_toggle_partition_flag: + * @cxt: fdisk context + * @partnum: partition number + * @flag: flag ID + * + * Returns: 0 on success, otherwise, a corresponding error. + */ +int fdisk_toggle_partition_flag(struct fdisk_context *cxt, + size_t partnum, + unsigned long flag) +{ + int rc; + + if (!cxt || !cxt->label) + return -EINVAL; + if (!cxt->label->op->part_toggle_flag) + return -ENOSYS; + + rc = cxt->label->op->part_toggle_flag(cxt, partnum, flag); + + DBG(CXT, ul_debugobj(cxt, "partition: %zd: toggle: 0x%04lx [rc=%d]", partnum, flag, rc)); + return rc; +} + +/** + * fdisk_reorder_partitions + * @cxt: fdisk context + * + * Sort partitions according to the partition start sector. + * + * Returns: 0 on success, otherwise, a corresponding error. + */ +int fdisk_reorder_partitions(struct fdisk_context *cxt) +{ + if (!cxt || !cxt->label) + return -EINVAL; + if (!cxt->label->op->reorder) + return -ENOSYS; + + return cxt->label->op->reorder(cxt); +} + +/* + * Resets the current used label driver to initial state + */ +void fdisk_deinit_label(struct fdisk_label *lb) +{ + assert(lb); + + /* private label information */ + if (lb->op->deinit) + lb->op->deinit(lb); +} + +/** + * fdisk_label_set_changed: + * @lb: label + * @changed: 0/1 + * + * Marks in-memory data as changed, to force fdisk_write_disklabel() to write + * to device. This should be unnecessar by default, the library keeps track + * about changes. + */ +void fdisk_label_set_changed(struct fdisk_label *lb, int changed) +{ + assert(lb); + lb->changed = changed ? 1 : 0; +} + +/** + * fdisk_label_is_changed: + * @lb: label + * + * Returns: 1 if in-memory data has been changed. + */ +int fdisk_label_is_changed(const struct fdisk_label *lb) +{ + assert(lb); + return lb ? lb->changed : 0; +} + +/** + * fdisk_label_set_disabled: + * @lb: label + * @disabled: 0 or 1 + * + * Mark label as disabled, then libfdisk is going to ignore the label when + * probe device for labels. + */ +void fdisk_label_set_disabled(struct fdisk_label *lb, int disabled) +{ + assert(lb); + + DBG(LABEL, ul_debug("%s label %s", + lb->name, + disabled ? "DISABLED" : "ENABLED")); + lb->disabled = disabled ? 1 : 0; +} + +/** + * fdisk_label_is_disabled: + * @lb: label + * + * Returns: 1 if label driver disabled. + */ +int fdisk_label_is_disabled(const struct fdisk_label *lb) +{ + assert(lb); + return lb ? lb->disabled : 0; +} diff --git a/libblkid/libfdisk/src/libfdisk.h b/libblkid/libfdisk/src/libfdisk.h new file mode 100644 index 000000000..844e17e19 --- /dev/null +++ b/libblkid/libfdisk/src/libfdisk.h @@ -0,0 +1,579 @@ +/* + * libfdisk.h - libfdisk API + * + * Copyright (C) 2012-2014 Karel Zak <kzak@redhat.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef _LIBFDISK_H +#define _LIBFDISK_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <stdio.h> +#include <stdarg.h> +#include <stdint.h> + +/** + * LIBFDISK_VERSION: + * + * Library version string + */ +#define LIBFDISK_VERSION "2.25.0" + +/** + * fdisk_context: + * + * Basic library handler. + */ +struct fdisk_context; + +/** + * fdisk_label: + * + * Disk label specific driver and setting. + */ +struct fdisk_label; + +/** + * fdisk_parttype: + * + * Partition type. + */ +struct fdisk_parttype; + +/** + * fdisk_partition: + * + * Partition abstraction (and template). + */ +struct fdisk_partition; + +/** + * fdisk_ask: + * + * Ask API handler for dialogs with users. + */ +struct fdisk_ask; + +/** + * fdisk_iter: + * + * Unified iterator. + */ +struct fdisk_iter; + +/** + * fdisk_table: + * + * Container for fdisk_partition objects + */ +struct fdisk_table; + +/** + * fdisk_field + * + * Output field description. + */ +struct fdisk_field; + +/** + * fdisk_script + * + * library handler for sfdisk compatible scripts + */ +struct fdisk_script; + +/** + * fdisk_sector_t + * + * LBA adresses type + */ +typedef uint64_t fdisk_sector_t; + +/** + * fdisk_labeltype: + * + * Supported partition table types (labels) + */ +enum fdisk_labeltype { + FDISK_DISKLABEL_DOS = (1 << 1), + FDISK_DISKLABEL_SUN = (1 << 2), + FDISK_DISKLABEL_SGI = (1 << 3), + FDISK_DISKLABEL_BSD = (1 << 4), + FDISK_DISKLABEL_GPT = (1 << 5) +}; + +/** + * fdisk_asktype: + * + * Ask API dialog types + */ +enum fdisk_asktype { + FDISK_ASKTYPE_NONE = 0, + FDISK_ASKTYPE_NUMBER, + FDISK_ASKTYPE_OFFSET, + FDISK_ASKTYPE_WARN, + FDISK_ASKTYPE_WARNX, + FDISK_ASKTYPE_INFO, + FDISK_ASKTYPE_YESNO, + FDISK_ASKTYPE_STRING, + FDISK_ASKTYPE_MENU +}; + +/* init.c */ +extern void fdisk_init_debug(int mask); + +/* context.h */ + +#define FDISK_PLURAL 0 +#define FDISK_SINGULAR 1 + +struct fdisk_context *fdisk_new_context(void); +struct fdisk_context *fdisk_new_nested_context(struct fdisk_context *parent, const char *name); +void fdisk_unref_context(struct fdisk_context *cxt); +void fdisk_ref_context(struct fdisk_context *cxt); + +struct fdisk_context *fdisk_get_parent(struct fdisk_context *cxt); +size_t fdisk_get_npartitions(struct fdisk_context *cxt); + +struct fdisk_label *fdisk_get_label(struct fdisk_context *cxt, const char *name); +int fdisk_next_label(struct fdisk_context *cxt, struct fdisk_label **lb); +size_t fdisk_get_nlabels(struct fdisk_context *cxt); + +int fdisk_has_label(struct fdisk_context *cxt); +int fdisk_is_labeltype(struct fdisk_context *cxt, enum fdisk_labeltype id); +#define fdisk_is_label(c, x) fdisk_is_labeltype(c, FDISK_DISKLABEL_ ## x) + + +int fdisk_assign_device(struct fdisk_context *cxt, + const char *fname, int readonly); +int fdisk_deassign_device(struct fdisk_context *cxt, int nosync); +int fdisk_is_readonly(struct fdisk_context *cxt); + +int fdisk_enable_details(struct fdisk_context *cxt, int enable); +int fdisk_is_details(struct fdisk_context *cxt); + +int fdisk_enable_listonly(struct fdisk_context *cxt, int enable); +int fdisk_is_listonly(struct fdisk_context *cxt); + +int fdisk_set_unit(struct fdisk_context *cxt, const char *str); +const char *fdisk_get_unit(struct fdisk_context *cxt, int n); +int fdisk_use_cylinders(struct fdisk_context *cxt); +unsigned int fdisk_get_units_per_sector(struct fdisk_context *cxt); + +unsigned long fdisk_get_optimal_iosize(struct fdisk_context *cxt); +unsigned long fdisk_get_minimal_iosize(struct fdisk_context *cxt); +unsigned long fdisk_get_physector_size(struct fdisk_context *cxt); +unsigned long fdisk_get_sector_size(struct fdisk_context *cxt); +unsigned long fdisk_get_alignment_offset(struct fdisk_context *cxt); +unsigned long fdisk_get_grain_size(struct fdisk_context *cxt); +fdisk_sector_t fdisk_get_first_lba(struct fdisk_context *cxt); +fdisk_sector_t fdisk_set_first_lba(struct fdisk_context *cxt, fdisk_sector_t lba); +fdisk_sector_t fdisk_get_last_lba(struct fdisk_context *cxt); +fdisk_sector_t fdisk_set_last_lba(struct fdisk_context *cxt, fdisk_sector_t lba); +fdisk_sector_t fdisk_get_nsectors(struct fdisk_context *cxt); +const char *fdisk_get_devname(struct fdisk_context *cxt); +int fdisk_get_devfd(struct fdisk_context *cxt); + +unsigned int fdisk_get_geom_heads(struct fdisk_context *cxt); +fdisk_sector_t fdisk_get_geom_sectors(struct fdisk_context *cxt); +fdisk_sector_t fdisk_get_geom_cylinders(struct fdisk_context *cxt); + + + +/* parttype.c */ +struct fdisk_parttype *fdisk_new_parttype(void); +void fdisk_ref_parttype(struct fdisk_parttype *t); +void fdisk_unref_parttype(struct fdisk_parttype *t); +int fdisk_parttype_set_name(struct fdisk_parttype *t, const char *str); +int fdisk_parttype_set_typestr(struct fdisk_parttype *t, const char *str); +int fdisk_parttype_set_code(struct fdisk_parttype *t, int code); +size_t fdisk_label_get_nparttypes(const struct fdisk_label *lb); +struct fdisk_parttype *fdisk_label_get_parttype(const struct fdisk_label *lb, size_t n); +int fdisk_label_has_code_parttypes(const struct fdisk_label *lb); +struct fdisk_parttype *fdisk_label_get_parttype_from_code( + const struct fdisk_label *lb, + unsigned int code); +struct fdisk_parttype *fdisk_label_get_parttype_from_string( + const struct fdisk_label *lb, + const char *str); +struct fdisk_parttype *fdisk_new_unknown_parttype(unsigned int code, + const char *typestr); +struct fdisk_parttype *fdisk_copy_parttype(const struct fdisk_parttype *type); +struct fdisk_parttype *fdisk_label_parse_parttype( + const struct fdisk_label *lb, + const char *str); +const char *fdisk_parttype_get_string(const struct fdisk_parttype *t); +unsigned int fdisk_parttype_get_code(const struct fdisk_parttype *t); +const char *fdisk_parttype_get_name(const struct fdisk_parttype *t); +int fdisk_parttype_is_unknown(const struct fdisk_parttype *t); + +/* label.c */ + +/** + * fdisk_fieldtype + * + * Types of fdisk_field + */ +enum fdisk_fieldtype { + FDISK_FIELD_NONE = 0, + + /* generic */ + FDISK_FIELD_DEVICE, + FDISK_FIELD_START, + FDISK_FIELD_END, + FDISK_FIELD_SECTORS, + FDISK_FIELD_CYLINDERS, + FDISK_FIELD_SIZE, + FDISK_FIELD_TYPE, + FDISK_FIELD_TYPEID, + + /* label specific */ + FDISK_FIELD_ATTR, + FDISK_FIELD_BOOT, + FDISK_FIELD_BSIZE, + FDISK_FIELD_CPG, + FDISK_FIELD_EADDR, + FDISK_FIELD_FSIZE, + FDISK_FIELD_NAME, + FDISK_FIELD_SADDR, + FDISK_FIELD_UUID, + + FDISK_NFIELDS /* must be last */ +}; + +int fdisk_label_get_type(const struct fdisk_label *lb); +const char *fdisk_label_get_name(const struct fdisk_label *lb); +int fdisk_label_require_geometry(const struct fdisk_label *lb); + + +extern int fdisk_write_disklabel(struct fdisk_context *cxt); +extern int fdisk_verify_disklabel(struct fdisk_context *cxt); +extern int fdisk_create_disklabel(struct fdisk_context *cxt, const char *name); +extern int fdisk_list_disklabel(struct fdisk_context *cxt); +extern int fdisk_locate_disklabel(struct fdisk_context *cxt, int n, const char **name, off_t *offset, size_t *size); + +extern int fdisk_get_disklabel_id(struct fdisk_context *cxt, char **id); +extern int fdisk_set_disklabel_id(struct fdisk_context *cxt); + +extern int fdisk_get_partition(struct fdisk_context *cxt, size_t partno, struct fdisk_partition **pa); +extern int fdisk_set_partition(struct fdisk_context *cxt, size_t partno, struct fdisk_partition *pa); +extern int fdisk_add_partition(struct fdisk_context *cxt, struct fdisk_partition *pa, size_t *partno); +extern int fdisk_delete_partition(struct fdisk_context *cxt, size_t partno); + +extern int fdisk_delete_all_partitions(struct fdisk_context *cxt); + +extern int fdisk_set_partition_type(struct fdisk_context *cxt, size_t partnum, + struct fdisk_parttype *t); + + +extern int fdisk_label_get_fields_ids( + const struct fdisk_label *lb, + struct fdisk_context *cxt, + int **ids, size_t *nids); + +extern const struct fdisk_field *fdisk_label_get_field(const struct fdisk_label *lb, int id); +extern const struct fdisk_field *fdisk_label_get_field_by_name( + const struct fdisk_label *lb, + const char *name); + +extern int fdisk_field_get_id(const struct fdisk_field *field); +extern const char *fdisk_field_get_name(const struct fdisk_field *field); +extern double fdisk_field_get_width(const struct fdisk_field *field); +extern int fdisk_field_is_number(const struct fdisk_field *field); + + +extern void fdisk_label_set_changed(struct fdisk_label *lb, int changed); +extern int fdisk_label_is_changed(const struct fdisk_label *lb); + +extern void fdisk_label_set_disabled(struct fdisk_label *lb, int disabled); +extern int fdisk_label_is_disabled(const struct fdisk_label *lb); + +extern int fdisk_is_partition_used(struct fdisk_context *cxt, size_t n); + +extern int fdisk_toggle_partition_flag(struct fdisk_context *cxt, size_t partnum, unsigned long flag); + +extern struct fdisk_partition *fdisk_new_partition(void); +extern void fdisk_reset_partition(struct fdisk_partition *pa); +extern void fdisk_ref_partition(struct fdisk_partition *pa); +extern void fdisk_unref_partition(struct fdisk_partition *pa); +extern int fdisk_partition_is_freespace(struct fdisk_partition *pa); + +int fdisk_partition_set_start(struct fdisk_partition *pa, uint64_t off); +int fdisk_partition_unset_start(struct fdisk_partition *pa); +uint64_t fdisk_partition_get_start(struct fdisk_partition *pa); +int fdisk_partition_has_start(struct fdisk_partition *pa); +int fdisk_partition_cmp_start(struct fdisk_partition *a, + struct fdisk_partition *b); +int fdisk_partition_start_follow_default(struct fdisk_partition *pa, int enable); +int fdisk_partition_start_is_default(struct fdisk_partition *pa); + +int fdisk_partition_set_size(struct fdisk_partition *pa, uint64_t sz); +int fdisk_partition_unset_size(struct fdisk_partition *pa); +uint64_t fdisk_partition_get_size(struct fdisk_partition *pa); +int fdisk_partition_has_size(struct fdisk_partition *pa); +int fdisk_partition_size_explicit(struct fdisk_partition *pa, int enable); + +int fdisk_partition_has_end(struct fdisk_partition *pa); +fdisk_sector_t fdisk_partition_get_end(struct fdisk_partition *pa); + +int fdisk_partition_set_partno(struct fdisk_partition *pa, size_t num); +int fdisk_partition_unset_partno(struct fdisk_partition *pa); +size_t fdisk_partition_get_partno(struct fdisk_partition *pa); +int fdisk_partition_has_partno(struct fdisk_partition *pa); +int fdisk_partition_cmp_partno(struct fdisk_partition *a, + struct fdisk_partition *b); +int fdisk_partition_partno_follow_default(struct fdisk_partition *pa, int enable); + + +extern int fdisk_partition_set_type(struct fdisk_partition *pa, struct fdisk_parttype *type); +extern struct fdisk_parttype *fdisk_partition_get_type(struct fdisk_partition *pa); +extern int fdisk_partition_set_name(struct fdisk_partition *pa, const char *name); +extern const char *fdisk_partition_get_name(struct fdisk_partition *pa); +extern int fdisk_partition_set_uuid(struct fdisk_partition *pa, const char *uuid); +extern int fdisk_partition_set_attrs(struct fdisk_partition *pa, const char *attrs); +extern const char *fdisk_partition_get_uuid(struct fdisk_partition *pa); +extern const char *fdisk_partition_get_attrs(struct fdisk_partition *pa); +extern int fdisk_partition_is_nested(struct fdisk_partition *pa); +extern int fdisk_partition_is_container(struct fdisk_partition *pa); +extern int fdisk_partition_get_parent(struct fdisk_partition *pa, size_t *parent); +extern int fdisk_partition_is_used(struct fdisk_partition *pa); +extern int fdisk_partition_is_bootable(struct fdisk_partition *pa); +extern int fdisk_partition_to_string(struct fdisk_partition *pa, + struct fdisk_context *cxt, + int id, char **data); + +int fdisk_partition_next_partno(struct fdisk_partition *pa, + struct fdisk_context *cxt, + size_t *n); + +extern int fdisk_partition_end_follow_default(struct fdisk_partition *pa, int enable); +extern int fdisk_partition_end_is_default(struct fdisk_partition *pa); + +extern int fdisk_reorder_partitions(struct fdisk_context *cxt); + +/* table.c */ +extern struct fdisk_table *fdisk_new_table(void); +extern int fdisk_reset_table(struct fdisk_table *tb); +extern void fdisk_ref_table(struct fdisk_table *tb); +extern void fdisk_unref_table(struct fdisk_table *tb); +extern size_t fdisk_table_get_nents(struct fdisk_table *tb); +extern int fdisk_table_is_empty(struct fdisk_table *tb); +extern int fdisk_table_add_partition(struct fdisk_table *tb, struct fdisk_partition *pa); +extern int fdisk_table_remove_partition(struct fdisk_table *tb, struct fdisk_partition *pa); + +extern int fdisk_get_partitions(struct fdisk_context *cxt, struct fdisk_table **tb); +extern int fdisk_get_freespaces(struct fdisk_context *cxt, struct fdisk_table **tb); + +extern int fdisk_table_wrong_order(struct fdisk_table *tb); +extern int fdisk_table_sort_partitions(struct fdisk_table *tb, + int (*cmp)(struct fdisk_partition *, + struct fdisk_partition *)); + +extern int fdisk_table_next_partition( + struct fdisk_table *tb, + struct fdisk_iter *itr, + struct fdisk_partition **pa); + +extern struct fdisk_partition *fdisk_table_get_partition( + struct fdisk_table *tb, + size_t n); +extern int fdisk_apply_table(struct fdisk_context *cxt, struct fdisk_table *tb); + +/* alignment.c */ +#define FDISK_ALIGN_UP 1 +#define FDISK_ALIGN_DOWN 2 +#define FDISK_ALIGN_NEAREST 3 + +fdisk_sector_t fdisk_align_lba(struct fdisk_context *cxt, fdisk_sector_t lba, int direction); +fdisk_sector_t fdisk_align_lba_in_range(struct fdisk_context *cxt, + fdisk_sector_t lba, fdisk_sector_t start, fdisk_sector_t stop); +int fdisk_lba_is_phy_aligned(struct fdisk_context *cxt, fdisk_sector_t lba); + +int fdisk_override_geometry(struct fdisk_context *cxt, + unsigned int cylinders, + unsigned int heads, + unsigned int sectors); +int fdisk_save_user_geometry(struct fdisk_context *cxt, + unsigned int cylinders, + unsigned int heads, + unsigned int sectors); +int fdisk_save_user_sector_size(struct fdisk_context *cxt, + unsigned int phy, + unsigned int log); +int fdisk_has_user_device_properties(struct fdisk_context *cxt); +int fdisk_reset_alignment(struct fdisk_context *cxt); +int fdisk_reset_device_properties(struct fdisk_context *cxt); +int fdisk_reread_partition_table(struct fdisk_context *cxt); + +/* iter.c */ +enum { + + FDISK_ITER_FORWARD = 0, + FDISK_ITER_BACKWARD +}; +extern struct fdisk_iter *fdisk_new_iter(int direction); +extern void fdisk_free_iter(struct fdisk_iter *itr); +extern void fdisk_reset_iter(struct fdisk_iter *itr, int direction); +extern int fdisk_iter_get_direction(struct fdisk_iter *itr); + + +/* dos.c */ +#define DOS_FLAG_ACTIVE 1 + +extern int fdisk_dos_move_begin(struct fdisk_context *cxt, size_t i); +extern int fdisk_dos_enable_compatible(struct fdisk_label *lb, int enable); +extern int fdisk_dos_is_compatible(struct fdisk_label *lb); + +/* sun.h */ +extern int fdisk_sun_set_alt_cyl(struct fdisk_context *cxt); +extern int fdisk_sun_set_xcyl(struct fdisk_context *cxt); +extern int fdisk_sun_set_ilfact(struct fdisk_context *cxt); +extern int fdisk_sun_set_rspeed(struct fdisk_context *cxt); +extern int fdisk_sun_set_pcylcount(struct fdisk_context *cxt); + +/* bsd.c */ +extern int fdisk_bsd_edit_disklabel(struct fdisk_context *cxt); +extern int fdisk_bsd_write_bootstrap(struct fdisk_context *cxt); +extern int fdisk_bsd_link_partition(struct fdisk_context *cxt); + +/* sgi.h */ +#define SGI_FLAG_BOOT 1 +#define SGI_FLAG_SWAP 2 +extern int fdisk_sgi_set_bootfile(struct fdisk_context *cxt); +extern int fdisk_sgi_create_info(struct fdisk_context *cxt); + +/* gpt */ + +/* GPT partition attributes */ +enum { + /* System partition (disk partitioning utilities must preserve the + * partition as is) */ + GPT_FLAG_REQUIRED = 1, + + /* EFI firmware should ignore the content of the partition and not try + * to read from it */ + GPT_FLAG_NOBLOCK, + + /* Legacy BIOS bootable */ + GPT_FLAG_LEGACYBOOT, + + /* bites 48-63, Defined and used by the individual partition type. + * + * The flag GPT_FLAG_GUIDSPECIFIC forces libfdisk to ask (by ask API) + * for a bit number. If you want to toggle specific bit and avoid any + * dialog, then use the bit number (in range 48..63). For example: + * + * // start dialog to ask for bit number + * fdisk_toggle_partition_flag(cxt, n, GPT_FLAG_GUIDSPECIFIC); + * + * // toggle bit 60 + * fdisk_toggle_partition_flag(cxt, n, 60); + */ + GPT_FLAG_GUIDSPECIFIC +}; + +extern int fdisk_gpt_is_hybrid(struct fdisk_context *cxt); + + +/* script.c */ +struct fdisk_script *fdisk_new_script(struct fdisk_context *cxt); +struct fdisk_script *fdisk_new_script_from_file(struct fdisk_context *cxt, + const char *filename); +void fdisk_ref_script(struct fdisk_script *dp); +void fdisk_unref_script(struct fdisk_script *dp); + +const char *fdisk_script_get_header(struct fdisk_script *dp, const char *name); +int fdisk_script_set_header(struct fdisk_script *dp, const char *name, const char *data); +struct fdisk_table *fdisk_script_get_table(struct fdisk_script *dp); +int fdisk_script_get_nlines(struct fdisk_script *dp); + +int fdisk_script_read_context(struct fdisk_script *dp, struct fdisk_context *cxt); +int fdisk_script_write_file(struct fdisk_script *dp, FILE *f); +int fdisk_script_read_file(struct fdisk_script *dp, FILE *f); +int fdisk_script_read_line(struct fdisk_script *dp, FILE *f, char *buf, size_t bufsz); + +int fdisk_set_script(struct fdisk_context *cxt, struct fdisk_script *dp); +struct fdisk_script *fdisk_get_script(struct fdisk_context *cxt); + +int fdisk_apply_script_headers(struct fdisk_context *cxt, struct fdisk_script *dp); +int fdisk_apply_script(struct fdisk_context *cxt, struct fdisk_script *dp); + + +/* ask.c */ +#define fdisk_is_ask(a, x) (fdisk_ask_get_type(a) == FDISK_ASKTYPE_ ## x) + +int fdisk_set_ask(struct fdisk_context *cxt, + int (*ask_cb)(struct fdisk_context *, struct fdisk_ask *, void *), + void *data); + + +void fdisk_ref_ask(struct fdisk_ask *ask); +void fdisk_unref_ask(struct fdisk_ask *ask); +const char *fdisk_ask_get_query(struct fdisk_ask *ask); +int fdisk_ask_get_type(struct fdisk_ask *ask); +const char *fdisk_ask_number_get_range(struct fdisk_ask *ask); +uint64_t fdisk_ask_number_get_default(struct fdisk_ask *ask); +uint64_t fdisk_ask_number_get_low(struct fdisk_ask *ask); +uint64_t fdisk_ask_number_get_high(struct fdisk_ask *ask); +uint64_t fdisk_ask_number_get_result(struct fdisk_ask *ask); +int fdisk_ask_number_set_result(struct fdisk_ask *ask, uint64_t result); +uint64_t fdisk_ask_number_get_base(struct fdisk_ask *ask); +uint64_t fdisk_ask_number_get_unit(struct fdisk_ask *ask); +int fdisk_ask_number_set_relative(struct fdisk_ask *ask, int relative); +int fdisk_ask_number_inchars(struct fdisk_ask *ask); +int fdisk_ask_partnum(struct fdisk_context *cxt, size_t *partnum, int wantnew); + +int fdisk_ask_number(struct fdisk_context *cxt, + uintmax_t low, + uintmax_t dflt, + uintmax_t high, + const char *query, + uintmax_t *result); +char *fdisk_ask_string_get_result(struct fdisk_ask *ask); +int fdisk_ask_string_set_result(struct fdisk_ask *ask, char *result); +int fdisk_ask_string(struct fdisk_context *cxt, + const char *query, + char **result); +int fdisk_ask_yesno(struct fdisk_context *cxt, + const char *query, + int *result); +int fdisk_ask_yesno_get_result(struct fdisk_ask *ask); +int fdisk_ask_yesno_set_result(struct fdisk_ask *ask, int result); +int fdisk_ask_menu_get_default(struct fdisk_ask *ask); +int fdisk_ask_menu_set_result(struct fdisk_ask *ask, int key); +int fdisk_ask_menu_get_result(struct fdisk_ask *ask, int *key); +int fdisk_ask_menu_get_item(struct fdisk_ask *ask, size_t idx, int *key, + const char **name, const char **desc); +size_t fdisk_ask_menu_get_nitems(struct fdisk_ask *ask); +int fdisk_ask_print_get_errno(struct fdisk_ask *ask); +const char *fdisk_ask_print_get_mesg(struct fdisk_ask *ask); + +int fdisk_info(struct fdisk_context *cxt, const char *fmt, ...); +int fdisk_warn(struct fdisk_context *cxt, const char *fmt, ...); +int fdisk_warnx(struct fdisk_context *cxt, const char *fmt, ...); + +/* utils.h */ +extern char *fdisk_partname(const char *dev, size_t partno); + +#ifdef __cplusplus +} +#endif + +#endif /* _LIBFDISK_H */ diff --git a/libblkid/libfdisk/src/libfdisk.h.in b/libblkid/libfdisk/src/libfdisk.h.in new file mode 100644 index 000000000..f82d5bd97 --- /dev/null +++ b/libblkid/libfdisk/src/libfdisk.h.in @@ -0,0 +1,579 @@ +/* + * libfdisk.h - libfdisk API + * + * Copyright (C) 2012-2014 Karel Zak <kzak@redhat.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef _LIBFDISK_H +#define _LIBFDISK_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <stdio.h> +#include <stdarg.h> +#include <stdint.h> + +/** + * LIBFDISK_VERSION: + * + * Library version string + */ +#define LIBFDISK_VERSION "@LIBFDISK_VERSION@" + +/** + * fdisk_context: + * + * Basic library handler. + */ +struct fdisk_context; + +/** + * fdisk_label: + * + * Disk label specific driver and setting. + */ +struct fdisk_label; + +/** + * fdisk_parttype: + * + * Partition type. + */ +struct fdisk_parttype; + +/** + * fdisk_partition: + * + * Partition abstraction (and template). + */ +struct fdisk_partition; + +/** + * fdisk_ask: + * + * Ask API handler for dialogs with users. + */ +struct fdisk_ask; + +/** + * fdisk_iter: + * + * Unified iterator. + */ +struct fdisk_iter; + +/** + * fdisk_table: + * + * Container for fdisk_partition objects + */ +struct fdisk_table; + +/** + * fdisk_field + * + * Output field description. + */ +struct fdisk_field; + +/** + * fdisk_script + * + * library handler for sfdisk compatible scripts + */ +struct fdisk_script; + +/** + * fdisk_sector_t + * + * LBA adresses type + */ +typedef uint64_t fdisk_sector_t; + +/** + * fdisk_labeltype: + * + * Supported partition table types (labels) + */ +enum fdisk_labeltype { + FDISK_DISKLABEL_DOS = (1 << 1), + FDISK_DISKLABEL_SUN = (1 << 2), + FDISK_DISKLABEL_SGI = (1 << 3), + FDISK_DISKLABEL_BSD = (1 << 4), + FDISK_DISKLABEL_GPT = (1 << 5) +}; + +/** + * fdisk_asktype: + * + * Ask API dialog types + */ +enum fdisk_asktype { + FDISK_ASKTYPE_NONE = 0, + FDISK_ASKTYPE_NUMBER, + FDISK_ASKTYPE_OFFSET, + FDISK_ASKTYPE_WARN, + FDISK_ASKTYPE_WARNX, + FDISK_ASKTYPE_INFO, + FDISK_ASKTYPE_YESNO, + FDISK_ASKTYPE_STRING, + FDISK_ASKTYPE_MENU +}; + +/* init.c */ +extern void fdisk_init_debug(int mask); + +/* context.h */ + +#define FDISK_PLURAL 0 +#define FDISK_SINGULAR 1 + +struct fdisk_context *fdisk_new_context(void); +struct fdisk_context *fdisk_new_nested_context(struct fdisk_context *parent, const char *name); +void fdisk_unref_context(struct fdisk_context *cxt); +void fdisk_ref_context(struct fdisk_context *cxt); + +struct fdisk_context *fdisk_get_parent(struct fdisk_context *cxt); +size_t fdisk_get_npartitions(struct fdisk_context *cxt); + +struct fdisk_label *fdisk_get_label(struct fdisk_context *cxt, const char *name); +int fdisk_next_label(struct fdisk_context *cxt, struct fdisk_label **lb); +size_t fdisk_get_nlabels(struct fdisk_context *cxt); + +int fdisk_has_label(struct fdisk_context *cxt); +int fdisk_is_labeltype(struct fdisk_context *cxt, enum fdisk_labeltype id); +#define fdisk_is_label(c, x) fdisk_is_labeltype(c, FDISK_DISKLABEL_ ## x) + + +int fdisk_assign_device(struct fdisk_context *cxt, + const char *fname, int readonly); +int fdisk_deassign_device(struct fdisk_context *cxt, int nosync); +int fdisk_is_readonly(struct fdisk_context *cxt); + +int fdisk_enable_details(struct fdisk_context *cxt, int enable); +int fdisk_is_details(struct fdisk_context *cxt); + +int fdisk_enable_listonly(struct fdisk_context *cxt, int enable); +int fdisk_is_listonly(struct fdisk_context *cxt); + +int fdisk_set_unit(struct fdisk_context *cxt, const char *str); +const char *fdisk_get_unit(struct fdisk_context *cxt, int n); +int fdisk_use_cylinders(struct fdisk_context *cxt); +unsigned int fdisk_get_units_per_sector(struct fdisk_context *cxt); + +unsigned long fdisk_get_optimal_iosize(struct fdisk_context *cxt); +unsigned long fdisk_get_minimal_iosize(struct fdisk_context *cxt); +unsigned long fdisk_get_physector_size(struct fdisk_context *cxt); +unsigned long fdisk_get_sector_size(struct fdisk_context *cxt); +unsigned long fdisk_get_alignment_offset(struct fdisk_context *cxt); +unsigned long fdisk_get_grain_size(struct fdisk_context *cxt); +fdisk_sector_t fdisk_get_first_lba(struct fdisk_context *cxt); +fdisk_sector_t fdisk_set_first_lba(struct fdisk_context *cxt, fdisk_sector_t lba); +fdisk_sector_t fdisk_get_last_lba(struct fdisk_context *cxt); +fdisk_sector_t fdisk_set_last_lba(struct fdisk_context *cxt, fdisk_sector_t lba); +fdisk_sector_t fdisk_get_nsectors(struct fdisk_context *cxt); +const char *fdisk_get_devname(struct fdisk_context *cxt); +int fdisk_get_devfd(struct fdisk_context *cxt); + +unsigned int fdisk_get_geom_heads(struct fdisk_context *cxt); +fdisk_sector_t fdisk_get_geom_sectors(struct fdisk_context *cxt); +fdisk_sector_t fdisk_get_geom_cylinders(struct fdisk_context *cxt); + + + +/* parttype.c */ +struct fdisk_parttype *fdisk_new_parttype(void); +void fdisk_ref_parttype(struct fdisk_parttype *t); +void fdisk_unref_parttype(struct fdisk_parttype *t); +int fdisk_parttype_set_name(struct fdisk_parttype *t, const char *str); +int fdisk_parttype_set_typestr(struct fdisk_parttype *t, const char *str); +int fdisk_parttype_set_code(struct fdisk_parttype *t, int code); +size_t fdisk_label_get_nparttypes(const struct fdisk_label *lb); +struct fdisk_parttype *fdisk_label_get_parttype(const struct fdisk_label *lb, size_t n); +int fdisk_label_has_code_parttypes(const struct fdisk_label *lb); +struct fdisk_parttype *fdisk_label_get_parttype_from_code( + const struct fdisk_label *lb, + unsigned int code); +struct fdisk_parttype *fdisk_label_get_parttype_from_string( + const struct fdisk_label *lb, + const char *str); +struct fdisk_parttype *fdisk_new_unknown_parttype(unsigned int code, + const char *typestr); +struct fdisk_parttype *fdisk_copy_parttype(const struct fdisk_parttype *type); +struct fdisk_parttype *fdisk_label_parse_parttype( + const struct fdisk_label *lb, + const char *str); +const char *fdisk_parttype_get_string(const struct fdisk_parttype *t); +unsigned int fdisk_parttype_get_code(const struct fdisk_parttype *t); +const char *fdisk_parttype_get_name(const struct fdisk_parttype *t); +int fdisk_parttype_is_unknown(const struct fdisk_parttype *t); + +/* label.c */ + +/** + * fdisk_fieldtype + * + * Types of fdisk_field + */ +enum fdisk_fieldtype { + FDISK_FIELD_NONE = 0, + + /* generic */ + FDISK_FIELD_DEVICE, + FDISK_FIELD_START, + FDISK_FIELD_END, + FDISK_FIELD_SECTORS, + FDISK_FIELD_CYLINDERS, + FDISK_FIELD_SIZE, + FDISK_FIELD_TYPE, + FDISK_FIELD_TYPEID, + + /* label specific */ + FDISK_FIELD_ATTR, + FDISK_FIELD_BOOT, + FDISK_FIELD_BSIZE, + FDISK_FIELD_CPG, + FDISK_FIELD_EADDR, + FDISK_FIELD_FSIZE, + FDISK_FIELD_NAME, + FDISK_FIELD_SADDR, + FDISK_FIELD_UUID, + + FDISK_NFIELDS /* must be last */ +}; + +int fdisk_label_get_type(const struct fdisk_label *lb); +const char *fdisk_label_get_name(const struct fdisk_label *lb); +int fdisk_label_require_geometry(const struct fdisk_label *lb); + + +extern int fdisk_write_disklabel(struct fdisk_context *cxt); +extern int fdisk_verify_disklabel(struct fdisk_context *cxt); +extern int fdisk_create_disklabel(struct fdisk_context *cxt, const char *name); +extern int fdisk_list_disklabel(struct fdisk_context *cxt); +extern int fdisk_locate_disklabel(struct fdisk_context *cxt, int n, const char **name, off_t *offset, size_t *size); + +extern int fdisk_get_disklabel_id(struct fdisk_context *cxt, char **id); +extern int fdisk_set_disklabel_id(struct fdisk_context *cxt); + +extern int fdisk_get_partition(struct fdisk_context *cxt, size_t partno, struct fdisk_partition **pa); +extern int fdisk_set_partition(struct fdisk_context *cxt, size_t partno, struct fdisk_partition *pa); +extern int fdisk_add_partition(struct fdisk_context *cxt, struct fdisk_partition *pa, size_t *partno); +extern int fdisk_delete_partition(struct fdisk_context *cxt, size_t partno); + +extern int fdisk_delete_all_partitions(struct fdisk_context *cxt); + +extern int fdisk_set_partition_type(struct fdisk_context *cxt, size_t partnum, + struct fdisk_parttype *t); + + +extern int fdisk_label_get_fields_ids( + const struct fdisk_label *lb, + struct fdisk_context *cxt, + int **ids, size_t *nids); + +extern const struct fdisk_field *fdisk_label_get_field(const struct fdisk_label *lb, int id); +extern const struct fdisk_field *fdisk_label_get_field_by_name( + const struct fdisk_label *lb, + const char *name); + +extern int fdisk_field_get_id(const struct fdisk_field *field); +extern const char *fdisk_field_get_name(const struct fdisk_field *field); +extern double fdisk_field_get_width(const struct fdisk_field *field); +extern int fdisk_field_is_number(const struct fdisk_field *field); + + +extern void fdisk_label_set_changed(struct fdisk_label *lb, int changed); +extern int fdisk_label_is_changed(const struct fdisk_label *lb); + +extern void fdisk_label_set_disabled(struct fdisk_label *lb, int disabled); +extern int fdisk_label_is_disabled(const struct fdisk_label *lb); + +extern int fdisk_is_partition_used(struct fdisk_context *cxt, size_t n); + +extern int fdisk_toggle_partition_flag(struct fdisk_context *cxt, size_t partnum, unsigned long flag); + +extern struct fdisk_partition *fdisk_new_partition(void); +extern void fdisk_reset_partition(struct fdisk_partition *pa); +extern void fdisk_ref_partition(struct fdisk_partition *pa); +extern void fdisk_unref_partition(struct fdisk_partition *pa); +extern int fdisk_partition_is_freespace(struct fdisk_partition *pa); + +int fdisk_partition_set_start(struct fdisk_partition *pa, uint64_t off); +int fdisk_partition_unset_start(struct fdisk_partition *pa); +uint64_t fdisk_partition_get_start(struct fdisk_partition *pa); +int fdisk_partition_has_start(struct fdisk_partition *pa); +int fdisk_partition_cmp_start(struct fdisk_partition *a, + struct fdisk_partition *b); +int fdisk_partition_start_follow_default(struct fdisk_partition *pa, int enable); +int fdisk_partition_start_is_default(struct fdisk_partition *pa); + +int fdisk_partition_set_size(struct fdisk_partition *pa, uint64_t sz); +int fdisk_partition_unset_size(struct fdisk_partition *pa); +uint64_t fdisk_partition_get_size(struct fdisk_partition *pa); +int fdisk_partition_has_size(struct fdisk_partition *pa); +int fdisk_partition_size_explicit(struct fdisk_partition *pa, int enable); + +int fdisk_partition_has_end(struct fdisk_partition *pa); +fdisk_sector_t fdisk_partition_get_end(struct fdisk_partition *pa); + +int fdisk_partition_set_partno(struct fdisk_partition *pa, size_t num); +int fdisk_partition_unset_partno(struct fdisk_partition *pa); +size_t fdisk_partition_get_partno(struct fdisk_partition *pa); +int fdisk_partition_has_partno(struct fdisk_partition *pa); +int fdisk_partition_cmp_partno(struct fdisk_partition *a, + struct fdisk_partition *b); +int fdisk_partition_partno_follow_default(struct fdisk_partition *pa, int enable); + + +extern int fdisk_partition_set_type(struct fdisk_partition *pa, struct fdisk_parttype *type); +extern struct fdisk_parttype *fdisk_partition_get_type(struct fdisk_partition *pa); +extern int fdisk_partition_set_name(struct fdisk_partition *pa, const char *name); +extern const char *fdisk_partition_get_name(struct fdisk_partition *pa); +extern int fdisk_partition_set_uuid(struct fdisk_partition *pa, const char *uuid); +extern int fdisk_partition_set_attrs(struct fdisk_partition *pa, const char *attrs); +extern const char *fdisk_partition_get_uuid(struct fdisk_partition *pa); +extern const char *fdisk_partition_get_attrs(struct fdisk_partition *pa); +extern int fdisk_partition_is_nested(struct fdisk_partition *pa); +extern int fdisk_partition_is_container(struct fdisk_partition *pa); +extern int fdisk_partition_get_parent(struct fdisk_partition *pa, size_t *parent); +extern int fdisk_partition_is_used(struct fdisk_partition *pa); +extern int fdisk_partition_is_bootable(struct fdisk_partition *pa); +extern int fdisk_partition_to_string(struct fdisk_partition *pa, + struct fdisk_context *cxt, + int id, char **data); + +int fdisk_partition_next_partno(struct fdisk_partition *pa, + struct fdisk_context *cxt, + size_t *n); + +extern int fdisk_partition_end_follow_default(struct fdisk_partition *pa, int enable); +extern int fdisk_partition_end_is_default(struct fdisk_partition *pa); + +extern int fdisk_reorder_partitions(struct fdisk_context *cxt); + +/* table.c */ +extern struct fdisk_table *fdisk_new_table(void); +extern int fdisk_reset_table(struct fdisk_table *tb); +extern void fdisk_ref_table(struct fdisk_table *tb); +extern void fdisk_unref_table(struct fdisk_table *tb); +extern size_t fdisk_table_get_nents(struct fdisk_table *tb); +extern int fdisk_table_is_empty(struct fdisk_table *tb); +extern int fdisk_table_add_partition(struct fdisk_table *tb, struct fdisk_partition *pa); +extern int fdisk_table_remove_partition(struct fdisk_table *tb, struct fdisk_partition *pa); + +extern int fdisk_get_partitions(struct fdisk_context *cxt, struct fdisk_table **tb); +extern int fdisk_get_freespaces(struct fdisk_context *cxt, struct fdisk_table **tb); + +extern int fdisk_table_wrong_order(struct fdisk_table *tb); +extern int fdisk_table_sort_partitions(struct fdisk_table *tb, + int (*cmp)(struct fdisk_partition *, + struct fdisk_partition *)); + +extern int fdisk_table_next_partition( + struct fdisk_table *tb, + struct fdisk_iter *itr, + struct fdisk_partition **pa); + +extern struct fdisk_partition *fdisk_table_get_partition( + struct fdisk_table *tb, + size_t n); +extern int fdisk_apply_table(struct fdisk_context *cxt, struct fdisk_table *tb); + +/* alignment.c */ +#define FDISK_ALIGN_UP 1 +#define FDISK_ALIGN_DOWN 2 +#define FDISK_ALIGN_NEAREST 3 + +fdisk_sector_t fdisk_align_lba(struct fdisk_context *cxt, fdisk_sector_t lba, int direction); +fdisk_sector_t fdisk_align_lba_in_range(struct fdisk_context *cxt, + fdisk_sector_t lba, fdisk_sector_t start, fdisk_sector_t stop); +int fdisk_lba_is_phy_aligned(struct fdisk_context *cxt, fdisk_sector_t lba); + +int fdisk_override_geometry(struct fdisk_context *cxt, + unsigned int cylinders, + unsigned int heads, + unsigned int sectors); +int fdisk_save_user_geometry(struct fdisk_context *cxt, + unsigned int cylinders, + unsigned int heads, + unsigned int sectors); +int fdisk_save_user_sector_size(struct fdisk_context *cxt, + unsigned int phy, + unsigned int log); +int fdisk_has_user_device_properties(struct fdisk_context *cxt); +int fdisk_reset_alignment(struct fdisk_context *cxt); +int fdisk_reset_device_properties(struct fdisk_context *cxt); +int fdisk_reread_partition_table(struct fdisk_context *cxt); + +/* iter.c */ +enum { + + FDISK_ITER_FORWARD = 0, + FDISK_ITER_BACKWARD +}; +extern struct fdisk_iter *fdisk_new_iter(int direction); +extern void fdisk_free_iter(struct fdisk_iter *itr); +extern void fdisk_reset_iter(struct fdisk_iter *itr, int direction); +extern int fdisk_iter_get_direction(struct fdisk_iter *itr); + + +/* dos.c */ +#define DOS_FLAG_ACTIVE 1 + +extern int fdisk_dos_move_begin(struct fdisk_context *cxt, size_t i); +extern int fdisk_dos_enable_compatible(struct fdisk_label *lb, int enable); +extern int fdisk_dos_is_compatible(struct fdisk_label *lb); + +/* sun.h */ +extern int fdisk_sun_set_alt_cyl(struct fdisk_context *cxt); +extern int fdisk_sun_set_xcyl(struct fdisk_context *cxt); +extern int fdisk_sun_set_ilfact(struct fdisk_context *cxt); +extern int fdisk_sun_set_rspeed(struct fdisk_context *cxt); +extern int fdisk_sun_set_pcylcount(struct fdisk_context *cxt); + +/* bsd.c */ +extern int fdisk_bsd_edit_disklabel(struct fdisk_context *cxt); +extern int fdisk_bsd_write_bootstrap(struct fdisk_context *cxt); +extern int fdisk_bsd_link_partition(struct fdisk_context *cxt); + +/* sgi.h */ +#define SGI_FLAG_BOOT 1 +#define SGI_FLAG_SWAP 2 +extern int fdisk_sgi_set_bootfile(struct fdisk_context *cxt); +extern int fdisk_sgi_create_info(struct fdisk_context *cxt); + +/* gpt */ + +/* GPT partition attributes */ +enum { + /* System partition (disk partitioning utilities must preserve the + * partition as is) */ + GPT_FLAG_REQUIRED = 1, + + /* EFI firmware should ignore the content of the partition and not try + * to read from it */ + GPT_FLAG_NOBLOCK, + + /* Legacy BIOS bootable */ + GPT_FLAG_LEGACYBOOT, + + /* bites 48-63, Defined and used by the individual partition type. + * + * The flag GPT_FLAG_GUIDSPECIFIC forces libfdisk to ask (by ask API) + * for a bit number. If you want to toggle specific bit and avoid any + * dialog, then use the bit number (in range 48..63). For example: + * + * // start dialog to ask for bit number + * fdisk_toggle_partition_flag(cxt, n, GPT_FLAG_GUIDSPECIFIC); + * + * // toggle bit 60 + * fdisk_toggle_partition_flag(cxt, n, 60); + */ + GPT_FLAG_GUIDSPECIFIC +}; + +extern int fdisk_gpt_is_hybrid(struct fdisk_context *cxt); + + +/* script.c */ +struct fdisk_script *fdisk_new_script(struct fdisk_context *cxt); +struct fdisk_script *fdisk_new_script_from_file(struct fdisk_context *cxt, + const char *filename); +void fdisk_ref_script(struct fdisk_script *dp); +void fdisk_unref_script(struct fdisk_script *dp); + +const char *fdisk_script_get_header(struct fdisk_script *dp, const char *name); +int fdisk_script_set_header(struct fdisk_script *dp, const char *name, const char *data); +struct fdisk_table *fdisk_script_get_table(struct fdisk_script *dp); +int fdisk_script_get_nlines(struct fdisk_script *dp); + +int fdisk_script_read_context(struct fdisk_script *dp, struct fdisk_context *cxt); +int fdisk_script_write_file(struct fdisk_script *dp, FILE *f); +int fdisk_script_read_file(struct fdisk_script *dp, FILE *f); +int fdisk_script_read_line(struct fdisk_script *dp, FILE *f, char *buf, size_t bufsz); + +int fdisk_set_script(struct fdisk_context *cxt, struct fdisk_script *dp); +struct fdisk_script *fdisk_get_script(struct fdisk_context *cxt); + +int fdisk_apply_script_headers(struct fdisk_context *cxt, struct fdisk_script *dp); +int fdisk_apply_script(struct fdisk_context *cxt, struct fdisk_script *dp); + + +/* ask.c */ +#define fdisk_is_ask(a, x) (fdisk_ask_get_type(a) == FDISK_ASKTYPE_ ## x) + +int fdisk_set_ask(struct fdisk_context *cxt, + int (*ask_cb)(struct fdisk_context *, struct fdisk_ask *, void *), + void *data); + + +void fdisk_ref_ask(struct fdisk_ask *ask); +void fdisk_unref_ask(struct fdisk_ask *ask); +const char *fdisk_ask_get_query(struct fdisk_ask *ask); +int fdisk_ask_get_type(struct fdisk_ask *ask); +const char *fdisk_ask_number_get_range(struct fdisk_ask *ask); +uint64_t fdisk_ask_number_get_default(struct fdisk_ask *ask); +uint64_t fdisk_ask_number_get_low(struct fdisk_ask *ask); +uint64_t fdisk_ask_number_get_high(struct fdisk_ask *ask); +uint64_t fdisk_ask_number_get_result(struct fdisk_ask *ask); +int fdisk_ask_number_set_result(struct fdisk_ask *ask, uint64_t result); +uint64_t fdisk_ask_number_get_base(struct fdisk_ask *ask); +uint64_t fdisk_ask_number_get_unit(struct fdisk_ask *ask); +int fdisk_ask_number_set_relative(struct fdisk_ask *ask, int relative); +int fdisk_ask_number_inchars(struct fdisk_ask *ask); +int fdisk_ask_partnum(struct fdisk_context *cxt, size_t *partnum, int wantnew); + +int fdisk_ask_number(struct fdisk_context *cxt, + uintmax_t low, + uintmax_t dflt, + uintmax_t high, + const char *query, + uintmax_t *result); +char *fdisk_ask_string_get_result(struct fdisk_ask *ask); +int fdisk_ask_string_set_result(struct fdisk_ask *ask, char *result); +int fdisk_ask_string(struct fdisk_context *cxt, + const char *query, + char **result); +int fdisk_ask_yesno(struct fdisk_context *cxt, + const char *query, + int *result); +int fdisk_ask_yesno_get_result(struct fdisk_ask *ask); +int fdisk_ask_yesno_set_result(struct fdisk_ask *ask, int result); +int fdisk_ask_menu_get_default(struct fdisk_ask *ask); +int fdisk_ask_menu_set_result(struct fdisk_ask *ask, int key); +int fdisk_ask_menu_get_result(struct fdisk_ask *ask, int *key); +int fdisk_ask_menu_get_item(struct fdisk_ask *ask, size_t idx, int *key, + const char **name, const char **desc); +size_t fdisk_ask_menu_get_nitems(struct fdisk_ask *ask); +int fdisk_ask_print_get_errno(struct fdisk_ask *ask); +const char *fdisk_ask_print_get_mesg(struct fdisk_ask *ask); + +int fdisk_info(struct fdisk_context *cxt, const char *fmt, ...); +int fdisk_warn(struct fdisk_context *cxt, const char *fmt, ...); +int fdisk_warnx(struct fdisk_context *cxt, const char *fmt, ...); + +/* utils.h */ +extern char *fdisk_partname(const char *dev, size_t partno); + +#ifdef __cplusplus +} +#endif + +#endif /* _LIBFDISK_H */ diff --git a/libblkid/libfdisk/src/libfdisk.sym b/libblkid/libfdisk/src/libfdisk.sym new file mode 100644 index 000000000..bf85d4e2a --- /dev/null +++ b/libblkid/libfdisk/src/libfdisk.sym @@ -0,0 +1,234 @@ +/* + * The symbol versioning ensures that a new application requiring symbol foo; + * can't run with old libblkid.so not providing foo; + * version info can't enforce this since we never change the SONAME. + * + * Copyright (C) 2014 Karel Zak <kzak@redhat.com> + */ +MOUNT_2.26 { +global: + fdisk_add_partition; + fdisk_align_lba; + fdisk_align_lba_in_range; + fdisk_apply_script; + fdisk_apply_script_headers; + fdisk_apply_table; + fdisk_ask_get_query; + fdisk_ask_get_type; + fdisk_ask_menu_get_default; + fdisk_ask_menu_get_item; + fdisk_ask_menu_get_nitems; + fdisk_ask_menu_get_result; + fdisk_ask_menu_set_result; + fdisk_ask_number; + fdisk_ask_number_get_base; + fdisk_ask_number_get_default; + fdisk_ask_number_get_high; + fdisk_ask_number_get_low; + fdisk_ask_number_get_range; + fdisk_ask_number_get_result; + fdisk_ask_number_get_unit; + fdisk_ask_number_inchars; + fdisk_ask_number_set_relative; + fdisk_ask_number_set_result; + fdisk_ask_partnum; + fdisk_ask_print_get_errno; + fdisk_ask_print_get_mesg; + fdisk_ask_string; + fdisk_ask_string_get_result; + fdisk_ask_string_set_result; + fdisk_ask_yesno; + fdisk_ask_yesno_get_result; + fdisk_ask_yesno_set_result; + fdisk_assign_device; + fdisk_bsd_edit_disklabel; + fdisk_bsd_link_partition; + fdisk_bsd_write_bootstrap; + fdisk_copy_parttype; + fdisk_create_disklabel; + fdisk_deassign_device; + fdisk_delete_all_partitions; + fdisk_delete_partition; + fdisk_dos_enable_compatible; + fdisk_dos_is_compatible; + fdisk_dos_move_begin; + fdisk_enable_details; + fdisk_enable_listonly; + fdisk_field_get_id; + fdisk_field_get_name; + fdisk_field_get_width; + fdisk_field_is_number; + fdisk_free_iter; + fdisk_get_alignment_offset; + fdisk_get_devfd; + fdisk_get_devname; + fdisk_get_disklabel_id; + fdisk_get_first_lba; + fdisk_get_freespaces; + fdisk_get_geom_cylinders; + fdisk_get_geom_heads; + fdisk_get_geom_sectors; + fdisk_get_grain_size; + fdisk_get_label; + fdisk_get_last_lba; + fdisk_get_minimal_iosize; + fdisk_get_nlabels; + fdisk_get_npartitions; + fdisk_get_nsectors; + fdisk_get_optimal_iosize; + fdisk_get_parent; + fdisk_get_partition; + fdisk_get_partitions; + fdisk_get_physector_size; + fdisk_get_script; + fdisk_get_sector_size; + fdisk_get_unit; + fdisk_get_units_per_sector; + fdisk_gpt_is_hybrid; + fdisk_has_label; + fdisk_has_user_device_properties; + fdisk_info; + fdisk_init_debug; + fdisk_is_details; + fdisk_is_labeltype; + fdisk_is_listonly; + fdisk_is_partition_used; + fdisk_is_readonly; + fdisk_iter_get_direction; + fdisk_label_get_field; + fdisk_label_get_field_by_name; + fdisk_label_get_fields_ids; + fdisk_label_get_name; + fdisk_label_get_nparttypes; + fdisk_label_get_parttype; + fdisk_label_get_parttype_from_code; + fdisk_label_get_parttype_from_string; + fdisk_label_get_type; + fdisk_label_has_code_parttypes; + fdisk_label_is_changed; + fdisk_label_is_disabled; + fdisk_label_parse_parttype; + fdisk_label_require_geometry; + fdisk_label_set_changed; + fdisk_label_set_disabled; + fdisk_lba_is_phy_aligned; + fdisk_list_disklabel; + fdisk_locate_disklabel; + fdisk_new_context; + fdisk_new_iter; + fdisk_new_nested_context; + fdisk_new_partition; + fdisk_new_parttype; + fdisk_new_script; + fdisk_new_script_from_file; + fdisk_new_table; + fdisk_new_unknown_parttype; + fdisk_next_label; + fdisk_override_geometry; + fdisk_partition_cmp_partno; + fdisk_partition_cmp_start; + fdisk_partition_end_follow_default; + fdisk_partition_end_is_default; + fdisk_partition_get_attrs; + fdisk_partition_get_end; + fdisk_partition_get_name; + fdisk_partition_get_parent; + fdisk_partition_get_partno; + fdisk_partition_get_size; + fdisk_partition_get_start; + fdisk_partition_get_type; + fdisk_partition_get_uuid; + fdisk_partition_has_end; + fdisk_partition_has_partno; + fdisk_partition_has_size; + fdisk_partition_has_start; + fdisk_partition_is_bootable; + fdisk_partition_is_container; + fdisk_partition_is_freespace; + fdisk_partition_is_nested; + fdisk_partition_is_used; + fdisk_partition_next_partno; + fdisk_partition_partno_follow_default; + fdisk_partition_set_attrs; + fdisk_partition_set_name; + fdisk_partition_set_partno; + fdisk_partition_set_size; + fdisk_partition_set_start; + fdisk_partition_set_type; + fdisk_partition_set_uuid; + fdisk_partition_size_explicit; + fdisk_partition_start_follow_default; + fdisk_partition_start_is_default; + fdisk_toggle_partition_flag; + fdisk_partition_to_string; + fdisk_partition_unset_partno; + fdisk_partition_unset_size; + fdisk_partition_unset_start; + fdisk_partname; + fdisk_parttype_get_code; + fdisk_parttype_get_name; + fdisk_parttype_get_string; + fdisk_parttype_is_unknown; + fdisk_parttype_set_code; + fdisk_parttype_set_name; + fdisk_parttype_set_typestr; + fdisk_ref_ask; + fdisk_ref_context; + fdisk_ref_partition; + fdisk_ref_parttype; + fdisk_ref_script; + fdisk_ref_table; + fdisk_reorder_partitions; + fdisk_reread_partition_table; + fdisk_reset_alignment; + fdisk_reset_device_properties; + fdisk_reset_iter; + fdisk_reset_partition; + fdisk_reset_table; + fdisk_save_user_geometry; + fdisk_save_user_sector_size; + fdisk_script_get_header; + fdisk_script_get_nlines; + fdisk_script_get_table; + fdisk_script_read_context; + fdisk_script_read_file; + fdisk_script_read_line; + fdisk_script_set_header; + fdisk_script_write_file; + fdisk_set_ask; + fdisk_set_disklabel_id; + fdisk_set_first_lba; + fdisk_set_last_lba; + fdisk_set_partition; + fdisk_set_partition_type; + fdisk_set_script; + fdisk_set_unit; + fdisk_sgi_create_info; + fdisk_sgi_set_bootfile; + fdisk_sun_set_alt_cyl; + fdisk_sun_set_ilfact; + fdisk_sun_set_pcylcount; + fdisk_sun_set_rspeed; + fdisk_sun_set_xcyl; + fdisk_table_add_partition; + fdisk_table_get_nents; + fdisk_table_get_partition; + fdisk_table_is_empty; + fdisk_table_next_partition; + fdisk_table_remove_partition; + fdisk_table_sort_partitions; + fdisk_table_wrong_order; + fdisk_unref_ask; + fdisk_unref_context; + fdisk_unref_partition; + fdisk_unref_parttype; + fdisk_unref_script; + fdisk_unref_table; + fdisk_use_cylinders; + fdisk_verify_disklabel; + fdisk_warn; + fdisk_warnx; + fdisk_write_disklabel; +local: + *; +}; diff --git a/libblkid/libfdisk/src/partition.c b/libblkid/libfdisk/src/partition.c new file mode 100644 index 000000000..8f8402716 --- /dev/null +++ b/libblkid/libfdisk/src/partition.c @@ -0,0 +1,963 @@ + +#include "c.h" +#include "strutils.h" + +#include "fdiskP.h" + +/** + * SECTION: partition + * @title: Partition + * @short_description: generic label independent partition abstraction + * + * The fdisk_partition provides label independent abstraction. The partitions + * are not directly connected with partition table (label) data. Any change to + * fdisk_partition does not affects in-memory or on-disk label data. + * + * The fdisk_partition is possible to use as a template for + * fdisk_add_partition() or fdisk_set_partition() operations. + */ + +static void init_partition(struct fdisk_partition *pa) +{ + FDISK_INIT_UNDEF(pa->size); + FDISK_INIT_UNDEF(pa->start); + FDISK_INIT_UNDEF(pa->partno); + FDISK_INIT_UNDEF(pa->parent_partno); + FDISK_INIT_UNDEF(pa->boot); + + INIT_LIST_HEAD(&pa->parts); +} + +/** + * fdisk_new_partition: + * + * Returns: new instance. + */ +struct fdisk_partition *fdisk_new_partition(void) +{ + struct fdisk_partition *pa = calloc(1, sizeof(*pa)); + + pa->refcount = 1; + init_partition(pa); + DBG(PART, ul_debugobj(pa, "alloc")); + return pa; +} + +/** + * fdisk_reset_partition: + * @pa: partition + * + * Resets partition content. + */ +void fdisk_reset_partition(struct fdisk_partition *pa) +{ + int ref; + + if (!pa) + return; + + DBG(PART, ul_debugobj(pa, "reset")); + ref = pa->refcount; + + fdisk_unref_parttype(pa->type); + free(pa->name); + free(pa->uuid); + free(pa->attrs); + + memset(pa, 0, sizeof(*pa)); + pa->refcount = ref; + + init_partition(pa); +} + +/** + * fdisk_ref_partition: + * @pa: partition pointer + * + * Incremparts reference counter. + */ +void fdisk_ref_partition(struct fdisk_partition *pa) +{ + if (pa) + pa->refcount++; +} + +/** + * fdisk_unref_partition: + * @pa: partition pointer + * + * De-incremparts reference counter, on zero the @pa is automatically + * deallocated. + */ +void fdisk_unref_partition(struct fdisk_partition *pa) +{ + if (!pa) + return; + + pa->refcount--; + if (pa->refcount <= 0) { + fdisk_reset_partition(pa); + list_del(&pa->parts); + DBG(PART, ul_debugobj(pa, "free")); + free(pa); + } +} + +/** + * fdisk_partition_set_start: + * @pa: partition + * @off: offset in sectors, maximal is UINT64_MAX-1 + * + * Note that zero is valid offset too. Use fdisk_partition_unset_start() to + * undefine the offset. + * + * Returns: 0 on success, <0 on error. + */ +int fdisk_partition_set_start(struct fdisk_partition *pa, fdisk_sector_t off) +{ + if (!pa) + return -EINVAL; + if (FDISK_IS_UNDEF(off)) + return -ERANGE; + pa->start = off; + return 0; +} + +/** + * fdisk_partition_unset_start: + * @pa: partition + * + * Sets the size as undefined. See fdisk_partition_has_start(). + * + * Returns: 0 on success, <0 on error. + */ +int fdisk_partition_unset_start(struct fdisk_partition *pa) +{ + if (!pa) + return -EINVAL; + FDISK_INIT_UNDEF(pa->start); + return 0; +} + +/** + * fdisk_partition_get_start: + * @pa: partition + * + * The zero is also valid offset. The function may return random undefined + * value when start offset is undefined (for example after + * fdisk_partition_unset_start()). Always use fdisk_partition_has_start() to be + * sure that you work with valid numbers. + * + * Returns: start offset in sectors + */ +fdisk_sector_t fdisk_partition_get_start(struct fdisk_partition *pa) +{ + return pa->start; +} + +/** + * fdisk_partition_has_start: + * @pa: partition + * + * Returns: 1 or 0 + */ +int fdisk_partition_has_start(struct fdisk_partition *pa) +{ + return pa && !FDISK_IS_UNDEF(pa->start); +} + + +/** + * fdisk_partition_cmp_start: + * @a: partition + * @b: partition + * + * Compares partitons according to start offset, See fdisk_sort_table(). + * + * Return: 0 if the same, <0 if @b greater, >0 if @a greater. + */ +int fdisk_partition_cmp_start(struct fdisk_partition *a, + struct fdisk_partition *b) +{ + int no_a = FDISK_IS_UNDEF(a->start), + no_b = FDISK_IS_UNDEF(b->start); + + if (no_a && no_b) + return 0; + if (no_a) + return -1; + if (no_b) + return 1; + + return cmp_numbers(a->start, b->start); +} + +/** + * fdisk_partition_start_follow_default + * @pa: partition + * @enable: 0|1 + * + * When @pa used as a tempalate for fdisk_add_partition() when force label driver + * to use the first possible space for the new partition. + * + * Returns: 0 on success, <0 on error. + */ +int fdisk_partition_start_follow_default(struct fdisk_partition *pa, int enable) +{ + if (!pa) + return -EINVAL; + pa->start_follow_default = enable ? 1 : 0; + return 0; +} + +/** + * fdisk_partition_start_is_default: + * @pa: partition + * + * See fdisk_partition_start_follow_default(). + * + * Returns: 1 if the partition follows default + */ +int fdisk_partition_start_is_default(struct fdisk_partition *pa) +{ + assert(pa); + return pa->start_follow_default; +} + + +/** + * fdisk_partition_set_size: + * @pa: partition + * @sz: size in sectors, maximal is UIN64_MAX-1 + * + * Note that zero is valid size too. Use fdisk_partition_unset_size() to + * undefine the size. + * + * Returns: 0 on success, <0 on error. + */ +int fdisk_partition_set_size(struct fdisk_partition *pa, fdisk_sector_t sz) +{ + if (!pa) + return -EINVAL; + if (FDISK_IS_UNDEF(sz)) + return -ERANGE; + pa->size = sz; + return 0; +} + +/** + * fdisk_partition_unset_size: + * @pa: partition + * + * Sets the size as undefined. See fdisk_partition_has_size(). + * + * Returns: 0 on success, <0 on error. + */ +int fdisk_partition_unset_size(struct fdisk_partition *pa) +{ + if (!pa) + return -EINVAL; + FDISK_INIT_UNDEF(pa->size); + return 0; +} + +/** + * fdisk_partition_get_size: + * @pa: partition + * + * The zero is also valid size. The function may return random undefined + * value when size is undefined (for example after fdisk_partition_unset_size()). + * Always use fdisk_partition_has_size() to be sure that you work with valid + * numbers. + * + * Returns: size offset in sectors + */ +fdisk_sector_t fdisk_partition_get_size(struct fdisk_partition *pa) +{ + return pa->size; +} + +/** + * fdisk_partition_has_size: + * @pa: partition + * + * Returns: 1 or 0 + */ +int fdisk_partition_has_size(struct fdisk_partition *pa) +{ + return pa && !FDISK_IS_UNDEF(pa->size); +} + +/** + * fdisk_partition_size_explicit: + * @pa: partition + * @enable: 0|1 + * + * By default libfdisk aligns the size when add the new partition (by + * fdisk_add_partrition()). If you want to disable this functionality use + * @enable = 1. + * + * Returns: 0 on success, <0 on error. + */ +int fdisk_partition_size_explicit(struct fdisk_partition *pa, int enable) +{ + if (!pa) + return -EINVAL; + pa->size_explicit = enable ? 1 : 0; + return 0; +} + +/** + * fdisk_partition_set_partno: + * @pa: partition + * @num: partitin number (0 is the first partition, maximal is SIZE_MAX-1) + * + * Note that zero is valid partno too. Use fdisk_partition_unset_partno() to + * undefine the partno. + * + * Returns: 0 on success, <0 on error. + */ +int fdisk_partition_set_partno(struct fdisk_partition *pa, size_t num) +{ + if (!pa) + return -EINVAL; + if (FDISK_IS_UNDEF(num)) + return -ERANGE; + pa->partno = num; + return 0; +} + +/** + * fdisk_partition_unset_partno: + * @pa: partition + * + * Sets the partno as undefined. See fdisk_partition_has_partno(). + * + * Returns: 0 on success, <0 on error. + */ +int fdisk_partition_unset_partno(struct fdisk_partition *pa) +{ + if (!pa) + return -EINVAL; + FDISK_INIT_UNDEF(pa->partno); + return 0; +} + +/** + * fdisk_partition_get_partno: + * @pa: partition + * + * The zero is also valid parition number. The function may return random + * value when partno is undefined (for example after fdisk_partition_unset_partno()). + * Always use fdisk_partition_has_partno() to be sure that you work with valid + * numbers. + * + * Returns: partition number (0 is the first partition) + */ +size_t fdisk_partition_get_partno(struct fdisk_partition *pa) +{ + return pa->partno; +} + +/** + * fdisk_partition_has_partno: + * @pa: partition + * + * Returns: 1 or 0 + */ +int fdisk_partition_has_partno(struct fdisk_partition *pa) +{ + return pa && !FDISK_IS_UNDEF(pa->partno); +} + + +/** + * fdisk_partition_cmp_partno: + * @a: partition + * @b: partition + * + * Compares partitons according to partition number See fdisk_sort_table(). + * + * Return: 0 if the same, <0 if @b greater, >0 if @a greater. + */ +int fdisk_partition_cmp_partno(struct fdisk_partition *a, + struct fdisk_partition *b) +{ + return a->partno - b->partno; +} + +/** + * fdisk_partition_partno_follow_default + * @pa: partition + * @enable: 0|1 + * + * When @pa used as a tempalate for fdisk_add_partition() when force label driver + * to add a new partition to the default (next) position. + * + * Returns: 0 on success, <0 on error. + */ +int fdisk_partition_partno_follow_default(struct fdisk_partition *pa, int enable) +{ + if (!pa) + return -EINVAL; + pa->partno_follow_default = enable ? 1 : 0; + return 0; +} + +/** + * fdisk_partition_set_type: + * @pa: partition + * @type: partition type + * + * Sets parition type. + * + * Returns: 0 on success, <0 on error. + */ +int fdisk_partition_set_type(struct fdisk_partition *pa, + struct fdisk_parttype *type) +{ + if (!pa) + return -EINVAL; + + fdisk_ref_parttype(type); + fdisk_unref_parttype(pa->type); + pa->type = type; + + return 0; +} + +/** + * fdisk_partition_get_type: + * @pa: partition + * + * Returns: pointer to partition type. + */ +struct fdisk_parttype *fdisk_partition_get_type(struct fdisk_partition *pa) +{ + return pa ? pa->type : NULL; +} + +int fdisk_partition_set_name(struct fdisk_partition *pa, const char *name) +{ + char *p = NULL; + + if (!pa) + return -EINVAL; + if (name) { + p = strdup(name); + if (!p) + return -ENOMEM; + } + free(pa->name); + pa->name = p; + return 0; +} + +const char *fdisk_partition_get_name(struct fdisk_partition *pa) +{ + return pa ? pa->name : NULL; +} + +int fdisk_partition_set_uuid(struct fdisk_partition *pa, const char *uuid) +{ + char *p = NULL; + + if (!pa) + return -EINVAL; + if (uuid) { + p = strdup(uuid); + if (!p) + return -ENOMEM; + } + free(pa->uuid); + pa->uuid = p; + return 0; +} + +/** + * fdisk_partition_has_end: + * @pa: partition + * + * Returns: 1 if the partition has defined last sector + */ +int fdisk_partition_has_end(struct fdisk_partition *pa) +{ + return pa && !FDISK_IS_UNDEF(pa->start) && !FDISK_IS_UNDEF(pa->size); +} + +/** + * fdisk_partition_get_end: + * @pa: partition + * + * This function may returns absolute non-sense, always check + * fdisk_partition_has_end(). + * + * Note that partition end is defined by fdisk_partition_set_start() and + * fdisk_partition_set_size(). + * + * Returns: last partition sector LBA. + */ +fdisk_sector_t fdisk_partition_get_end(struct fdisk_partition *pa) +{ + return pa->start + pa->size - (pa->size == 0 ? 0 : 1); +} + +/** + * fdisk_partition_end_follow_default + * @pa: partition + * @enable: 0|1 + * + * When @pa used as a tempalate for fdisk_add_partition() when force label driver + * to use all the possible space for the new partition. + * + * Returns: 0 on success, <0 on error. + */ +int fdisk_partition_end_follow_default(struct fdisk_partition *pa, int enable) +{ + if (!pa) + return -EINVAL; + pa->end_follow_default = enable ? 1 : 0; + return 0; +} + +/** + * fdisk_partition_end_is_default: + * @pa: partition + * + * Returns: 1 if the partition follows default + */ +int fdisk_partition_end_is_default(struct fdisk_partition *pa) +{ + assert(pa); + return pa->end_follow_default; +} + +const char *fdisk_partition_get_uuid(struct fdisk_partition *pa) +{ + return pa ? pa->uuid : NULL; +} + +const char *fdisk_partition_get_attrs(struct fdisk_partition *pa) +{ + return pa ? pa->attrs : NULL; +} + +int fdisk_partition_set_attrs(struct fdisk_partition *pa, const char *attrs) +{ + char *p = NULL; + + if (!pa) + return -EINVAL; + if (attrs) { + p = strdup(attrs); + if (!p) + return -ENOMEM; + } + free(pa->attrs); + pa->attrs = p; + return 0; +} + +int fdisk_partition_is_nested(struct fdisk_partition *pa) +{ + return pa && !FDISK_IS_UNDEF(pa->parent_partno); +} + +int fdisk_partition_is_container(struct fdisk_partition *pa) +{ + return pa && pa->container; +} + +int fdisk_partition_get_parent(struct fdisk_partition *pa, size_t *parent) +{ + if (pa && parent) + *parent = pa->parent_partno; + else + return -EINVAL; + return 0; +} + +int fdisk_partition_is_used(struct fdisk_partition *pa) +{ + return pa && pa->used; +} + +int fdisk_partition_is_bootable(struct fdisk_partition *pa) +{ + return pa && pa->boot == 1; +} + +int fdisk_partition_is_freespace(struct fdisk_partition *pa) +{ + return pa && pa->freespace; +} + +int fdisk_partition_next_partno( + struct fdisk_partition *pa, + struct fdisk_context *cxt, + size_t *n) +{ + assert(cxt); + assert(n); + + if (pa && pa->partno_follow_default) { + size_t i; + + DBG(PART, ul_debugobj(pa, "next partno (follow default)")); + + for (i = 0; i < cxt->label->nparts_max; i++) { + if (!fdisk_is_partition_used(cxt, i)) { + *n = i; + return 0; + } + } + return -ERANGE; + + } else if (pa && fdisk_partition_has_partno(pa)) { + + DBG(PART, ul_debugobj(pa, "next partno (specified=%zu)", pa->partno)); + + if (pa->partno >= cxt->label->nparts_max) + return -ERANGE; + *n = pa->partno; + } else + return fdisk_ask_partnum(cxt, n, 1); + + return 0; +} + +/** + * fdisk_partition_to_string: + * @pa: partition + * @cxt: context + * @id: field (FDISK_FIELD_*) + * @data: returns string with allocated data + * + * Returns info about partition converted to printable string. + * + * For example + * <informalexample> + * <programlisting> + * struct fdisk_parition *pa; + * + * fdisk_get_partition(cxt, 0, &pa); + * fdisk_partition_to_string(pa, FDISK_FIELD_UUID, &data); + * printf("first partition uuid: %s\n", data); + * free(data); + * fdisk_unref_partition(pa); + * </programlisting> + * </informalexample> + * + * returns UUID for the first partition. + * + * Returns: 0 on success, otherwise, a corresponding error. + */ +int fdisk_partition_to_string(struct fdisk_partition *pa, + struct fdisk_context *cxt, + int id, + char **data) +{ + char *p = NULL; + int rc = 0; + uint64_t x; + + if (!pa || !cxt) + return -EINVAL; + + switch (id) { + case FDISK_FIELD_DEVICE: + if (pa->freespace) + p = strdup(_("Free space")); + else if (fdisk_partition_has_partno(pa) && cxt->dev_path) { + if (cxt->label->flags & FDISK_LABEL_FL_INCHARS_PARTNO) + rc = asprintf(&p, "%c", (int) pa->partno + 'a'); + else + p = fdisk_partname(cxt->dev_path, pa->partno + 1); + } + break; + case FDISK_FIELD_BOOT: + if (fdisk_partition_is_bootable(pa)) + rc = asprintf(&p, "%c", pa->boot ? '*' : ' '); + break; + case FDISK_FIELD_START: + if (fdisk_partition_has_start(pa)) { + x = fdisk_cround(cxt, pa->start); + rc = pa->start_post ? + asprintf(&p, "%ju%c", x, pa->start_post) : + asprintf(&p, "%ju", x); + } + break; + case FDISK_FIELD_END: + if (fdisk_partition_has_end(pa)) { + x = fdisk_cround(cxt, fdisk_partition_get_end(pa)); + rc = pa->end_post ? + asprintf(&p, "%ju%c", x, pa->end_post) : + asprintf(&p, "%ju", x); + } + break; + case FDISK_FIELD_SIZE: + if (fdisk_partition_has_size(pa)) { + uint64_t sz = pa->size * cxt->sector_size; + + if (fdisk_is_details(cxt)) { + rc = pa->size_post ? + asprintf(&p, "%ju%c", sz, pa->size_post) : + asprintf(&p, "%ju", sz); + } else { + p = size_to_human_string(SIZE_SUFFIX_1LETTER, sz); + if (!p) + rc = -ENOMEM; + } + } + break; + case FDISK_FIELD_CYLINDERS: + rc = asprintf(&p, "%ju", (uintmax_t) + fdisk_cround(cxt, fdisk_partition_has_size(pa) ? pa->size : 0)); + break; + case FDISK_FIELD_SECTORS: + rc = asprintf(&p, "%ju", + fdisk_partition_has_size(pa) ? (uintmax_t) pa->size : 0); + break; + case FDISK_FIELD_BSIZE: + rc = asprintf(&p, "%ju", pa->bsize); + break; + case FDISK_FIELD_FSIZE: + rc = asprintf(&p, "%ju", pa->fsize); + break; + case FDISK_FIELD_CPG: + rc = asprintf(&p, "%ju", pa->cpg); + break; + case FDISK_FIELD_TYPE: + p = pa->type && pa->type->name ? strdup(pa->type->name) : NULL; + break; + case FDISK_FIELD_TYPEID: + if (pa->type && fdisk_parttype_get_string(pa->type)) + rc = asprintf(&p, "%s", fdisk_parttype_get_string(pa->type)); + else if (pa->type) + rc = asprintf(&p, "%x", fdisk_parttype_get_code(pa->type)); + break; + case FDISK_FIELD_UUID: + p = pa->uuid ? strdup(pa->uuid) : NULL; + break; + case FDISK_FIELD_NAME: + p = pa->name ? strdup(pa->name) : NULL; + break; + case FDISK_FIELD_ATTR: + p = pa->attrs ? strdup(pa->attrs) : NULL; + break; + case FDISK_FIELD_SADDR: + p = pa->start_chs ? strdup(pa->start_chs) : NULL; + break; + case FDISK_FIELD_EADDR: + p = pa->end_chs ? strdup(pa->end_chs) : NULL; + break; + default: + return -EINVAL; + } + + if (rc < 0) + rc = -ENOMEM; + else if (rc > 0) + rc = 0; + + if (data) + *data = p; + return rc; +} + +/** + * fdisk_get_partition: + * @cxt: context + * @partno: partition number (0 is the first partition) + * @pa: returns data about partition + * + * Reads disklabel and fills in @pa with data about partition @n. + * + * Note that partno may address unused partition and then this function does + * not fill anything to @pa. See fdisk_is_partition_used(). If @pa points to + * NULL then the function allocates a newly allocated fdisk_partition struct, + * use fdisk_unref_partition() to deallocate. + * + * Returns: 0 on success, otherwise, a corresponding error. + */ +int fdisk_get_partition(struct fdisk_context *cxt, size_t partno, + struct fdisk_partition **pa) +{ + int rc; + struct fdisk_partition *np = NULL; + + if (!cxt || !cxt->label || !pa) + return -EINVAL; + if (!cxt->label->op->get_part) + return -ENOSYS; + if (!fdisk_is_partition_used(cxt, partno)) + return -EINVAL; + + if (!*pa) { + np = *pa = fdisk_new_partition(); + if (!*pa) + return -ENOMEM; + } else + fdisk_reset_partition(*pa); + + (*pa)->partno = partno; + rc = cxt->label->op->get_part(cxt, partno, *pa); + + if (rc) { + if (np) { + fdisk_unref_partition(np); + *pa = NULL; + } else + fdisk_reset_partition(*pa); + } else + (*pa)->size_explicit = 1; + return rc; +} + +/** + * fdisk_set_partition: + * @cxt: context + * @partno: partition number (0 is the first partition) + * @pa: new partition setting + * + * Modifies disklabel according to setting with in @pa. + * + * Returns: 0 on success, <0 on error. + */ +int fdisk_set_partition(struct fdisk_context *cxt, size_t partno, + struct fdisk_partition *pa) +{ + if (!cxt || !cxt->label || !pa) + return -EINVAL; + if (!cxt->label->op->set_part) + return -ENOSYS; + + DBG(CXT, ul_debugobj(cxt, "setting partition %zu %p (start=%ju, end=%ju, size=%ju, " + "defaults(start=%s, end=%s, partno=%s)", + partno, pa, + (uintmax_t) fdisk_partition_get_start(pa), + (uintmax_t) fdisk_partition_get_end(pa), + (uintmax_t) fdisk_partition_get_size(pa), + pa->start_follow_default ? "yes" : "no", + pa->end_follow_default ? "yes" : "no", + pa->partno_follow_default ? "yes" : "no")); + + return cxt->label->op->set_part(cxt, partno, pa); +} + +/** + * fdisk_add_partition: + * @cxt: fdisk context + * @pa: template for the partition (or NULL) + * @partno: NULL or returns new partition number + * + * If @pa is not specified or any @pa item is missiong the libfdisk will ask by + * fdisk_ask_ API. + * + * Adds a new partition to disklabel. + * + * Returns: 0 on success, <0 on error. + */ +int fdisk_add_partition(struct fdisk_context *cxt, + struct fdisk_partition *pa, + size_t *partno) +{ + int rc; + + assert(cxt); + assert(cxt->label); + + if (!cxt || !cxt->label) + return -EINVAL; + if (!cxt->label->op->add_part) + return -ENOSYS; + if (fdisk_missing_geometry(cxt)) + return -EINVAL; + + if (pa) + DBG(CXT, ul_debugobj(cxt, "adding new partition %p (start=%ju, end=%ju, size=%ju, " + "defaults(start=%s, end=%s, partno=%s)", + pa, + (uintmax_t) fdisk_partition_get_start(pa), + (uintmax_t) fdisk_partition_get_end(pa), + (uintmax_t) fdisk_partition_get_size(pa), + pa->start_follow_default ? "yes" : "no", + pa->end_follow_default ? "yes" : "no", + pa->partno_follow_default ? "yes" : "no")); + else + DBG(CXT, ul_debugobj(cxt, "adding partition")); + + rc = cxt->label->op->add_part(cxt, pa, partno); + + DBG(CXT, ul_debugobj(cxt, "add partition done (rc=%d)", rc)); + return rc; +} + +/** + * fdisk_delete_partition: + * @cxt: fdisk context + * @partno: partition number to delete (0 is the first partition) + * + * Deletes a @partno partition from disklabel. + * + * Returns: 0 on success, <0 on error + */ +int fdisk_delete_partition(struct fdisk_context *cxt, size_t partno) +{ + if (!cxt || !cxt->label) + return -EINVAL; + if (!cxt->label->op->del_part) + return -ENOSYS; + + DBG(CXT, ul_debugobj(cxt, "deleting %s partition number %zd", + cxt->label->name, partno)); + return cxt->label->op->del_part(cxt, partno); +} + +/** + * fdisk_delete_all_partitions: + * @cxt: fdisk context + * + * Delete all used partitions from disklabel. + * + * Returns: 0 on success, otherwise, a corresponding error. + */ +int fdisk_delete_all_partitions(struct fdisk_context *cxt) +{ + size_t i; + int rc; + + if (!cxt || !cxt->label) + return -EINVAL; + + for (i = 0; i < cxt->label->nparts_max; i++) { + + if (!fdisk_is_partition_used(cxt, i)) + continue; + rc = fdisk_delete_partition(cxt, i); + if (rc) + break; + } + + return rc; +} + +/** + * fdisk_is_partition_used: + * @cxt: context + * @n: partition number (0 is the first partition) + * + * This is faster than fdisk_get_partition() + fdisk_partition_is_used(). + * + * Returns: 0 or 1 + */ +int fdisk_is_partition_used(struct fdisk_context *cxt, size_t n) +{ + if (!cxt || !cxt->label) + return -EINVAL; + if (!cxt->label->op->part_is_used) + return -ENOSYS; + + return cxt->label->op->part_is_used(cxt, n); +} + diff --git a/libblkid/libfdisk/src/parttype.c b/libblkid/libfdisk/src/parttype.c new file mode 100644 index 000000000..aedf4e83b --- /dev/null +++ b/libblkid/libfdisk/src/parttype.c @@ -0,0 +1,401 @@ + +#include <ctype.h> + +#include "nls.h" +#include "fdiskP.h" + +/** + * SECTION: parttype + * @title: Partition types + * @short_description: abstraction to partition types + * + * There are two basic types of parttypes, string based (e.g. GPT) + * and code/hex based (e.g. MBR). + */ + +/** + * fdisk_new_parttype: + * + * It's recommended to use fdisk_label_get_parttype_from_code() or + * fdisk_label_get_parttype_from_string() for well known types rather + * than allocate a new instance. + * + * Returns: new instance. + */ +struct fdisk_parttype *fdisk_new_parttype(void) +{ + struct fdisk_parttype *t = calloc(1, sizeof(*t)); + + t->refcount = 1; + t->flags = FDISK_PARTTYPE_ALLOCATED; + DBG(PARTTYPE, ul_debugobj(t, "alloc")); + return t; +} + +/** + * fdisk_ref_parttype: + * @t: partition type + * + * Incremparts reference counter for allocated types + */ +void fdisk_ref_parttype(struct fdisk_parttype *t) +{ + if (fdisk_parttype_is_allocated(t)) + t->refcount++; +} + +/** + * fdisk_unref_parttype + * @t: partition pointer + * + * De-incremparts reference counter, on zero the @t is automatically + * deallocated. + */ +void fdisk_unref_parttype(struct fdisk_parttype *t) +{ + if (!fdisk_parttype_is_allocated(t)) + return; + + t->refcount--; + if (t->refcount <= 0) { + DBG(PARTTYPE, ul_debugobj(t, "free")); + free(t->typestr); + free(t->name); + free(t); + } +} + +/** + * fdisk_parttype_set_name: + * @t: partition type + * @str: type name + * + * Sets type name to allocated partition type, for static types + * it returns -EINVAL. + * + * Return: 0 on success, <0 on error + */ +int fdisk_parttype_set_name(struct fdisk_parttype *t, const char *str) +{ + char *p = NULL; + + if (!t || !fdisk_parttype_is_allocated(t)) + return -EINVAL; + if (str) { + p = strdup(str); + if (!p) + return -ENOMEM; + } + + free(t->name); + t->name = p; + return 0; +} + +/** + * fdisk_parttype_set_typestr: + * @t: partition type + * @str: type identificator (e.g. GUID for GPT) + * + * Sets type string to allocated partition type, for static types + * it returns -EINVAL. Don't use this function for MBR, see + * fdisk_parttype_set_code(). + * + * Return: 0 on success, <0 on error + */ +int fdisk_parttype_set_typestr(struct fdisk_parttype *t, const char *str) +{ + char *p = NULL; + + if (!t || !fdisk_parttype_is_allocated(t)) + return -EINVAL; + if (str) { + p = strdup(str); + if (!p) + return -ENOMEM; + } + + free(t->typestr); + t->typestr = p; + return 0; +} + +/** + * fdisk_parttype_set_code: + * @t: partition type + * @code: type identificator (e.g. MBR type codes) + * + * Sets type code to allocated partition type, for static types it returns + * -EINVAL. Don't use this function for GPT, see fdisk_parttype_set_typestr(). + * + * Return: 0 on success, <0 on error + */ +int fdisk_parttype_set_code(struct fdisk_parttype *t, int code) +{ + if (!t || !fdisk_parttype_is_allocated(t)) + return -EINVAL; + t->code = code; + return 0; +} + +/** + * fdisk_label_get_nparttypes: + * @lb: label + * + * Returns: number of types supported by label. + */ +size_t fdisk_label_get_nparttypes(const struct fdisk_label *lb) +{ + if (!lb) + return 0; + return lb->nparttypes; +} + +/** + * fdisk_label_get_parttype: + * @lb: label + * @n: number + * + * Returns: return parttype + */ +struct fdisk_parttype *fdisk_label_get_parttype(const struct fdisk_label *lb, size_t n) +{ + if (!lb || n >= lb->nparttypes) + return NULL; + return &lb->parttypes[n]; +} + +/** + * fdisk_label_has_code_parttypes: + * @lb: label + * + * Returns: 1 if the label uses code as partition type + * identifiers (e.g. MBR) or 0. + */ +int fdisk_label_has_code_parttypes(const struct fdisk_label *lb) +{ + assert(lb); + + if (lb->parttypes && lb->parttypes[0].typestr) + return 0; + return 1; +} + + +/** + * fdisk_label_get_parttype_from_code: + * @lb: label + * @code: code to search for + * + * Search for partition type in label-specific table. The result + * is pointer to static array of label types. + * + * Returns: partition type or NULL upon failure or invalid @code. + */ +struct fdisk_parttype *fdisk_label_get_parttype_from_code( + const struct fdisk_label *lb, + unsigned int code) +{ + size_t i; + + assert(lb); + + if (!lb->nparttypes) + return NULL; + + for (i = 0; i < lb->nparttypes; i++) + if (lb->parttypes[i].code == code) + return &lb->parttypes[i]; + return NULL; +} + +/** + * fdisk_label_get_parttype_from_string: + * @lb: label + * @str: string to search for + * + * Search for partition type in label-specific table. The result + * is pointer to static array of label types. + * + * Returns: partition type or NULL upon failure or invalid @str. + */ +struct fdisk_parttype *fdisk_label_get_parttype_from_string( + const struct fdisk_label *lb, + const char *str) +{ + size_t i; + + assert(lb); + + if (!lb->nparttypes) + return NULL; + + for (i = 0; i < lb->nparttypes; i++) + if (lb->parttypes[i].typestr + && strcasecmp(lb->parttypes[i].typestr, str) == 0) + return &lb->parttypes[i]; + + return NULL; +} + +/** + * fdisk_new_unknown_parttype: + * @code: type as number + * @typestr: type as string + + * Allocates new 'unknown' partition type. Use fdisk_unref_parttype() to + * deallocate. + * + * Returns: newly allocated partition type, or NULL upon failure. + */ +struct fdisk_parttype *fdisk_new_unknown_parttype(unsigned int code, + const char *typestr) +{ + struct fdisk_parttype *t = fdisk_new_parttype(); + + if (!t) + return NULL; + + fdisk_parttype_set_name(t, _("unknown")); + fdisk_parttype_set_code(t, code); + fdisk_parttype_set_typestr(t, typestr); + t->flags |= FDISK_PARTTYPE_UNKNOWN; + + return t; +} + +/** + * fdisk_copy_parttype: + * @type: type to copy + * + * Use fdisk_unref_parttype() to deallocate. + * + * Returns: newly allocated partition type, or NULL upon failure. + */ +struct fdisk_parttype *fdisk_copy_parttype(const struct fdisk_parttype *type) +{ + struct fdisk_parttype *t = fdisk_new_parttype(); + + if (!t) + return NULL; + + fdisk_parttype_set_name(t, type->name); + fdisk_parttype_set_code(t, type->code); + fdisk_parttype_set_typestr(t, type->typestr); + + return t; +} + +/** + * fdisk_label_parse_parttype: + * @lb: label + * @str: string to parse from + * + * Parses partition type from @str according to the label. Thefunction returns + * a pointer to static table of the partition types, or newly allocated + * partition type for unknown types (see fdisk_parttype_is_unknown(). It's + * safe to call fdisk_unref_parttype() for all results. + * + * Returns: pointer to type or NULL on error. + */ +struct fdisk_parttype *fdisk_label_parse_parttype( + const struct fdisk_label *lb, + const char *str) +{ + struct fdisk_parttype *types, *ret; + unsigned int code = 0; + char *typestr = NULL, *end = NULL; + + assert(lb); + + if (!lb->nparttypes) + return NULL; + + DBG(LABEL, ul_debugobj((void *) lb, "parsing '%s' (%s) partition type", + str, lb->name)); + types = lb->parttypes; + + if (types[0].typestr == NULL && isxdigit(*str)) { + + errno = 0; + code = strtol(str, &end, 16); + + if (errno || *end != '\0') { + DBG(LABEL, ul_debugobj((void *) lb, "parsing failed: %m")); + return NULL; + } + ret = fdisk_label_get_parttype_from_code(lb, code); + if (ret) + goto done; + } else { + int i; + + /* maybe specified by type string (e.g. UUID) */ + ret = fdisk_label_get_parttype_from_string(lb, str); + if (ret) + goto done; + + /* maybe specified by order number */ + errno = 0; + i = strtol(str, &end, 0); + if (errno == 0 && *end == '\0' && i > 0 + && i - 1 < (int) lb->nparttypes) { + ret = &types[i - 1]; + goto done; + } + } + + ret = fdisk_new_unknown_parttype(code, typestr); +done: + DBG(PARTTYPE, ul_debugobj(ret, "returns parsed '%s' partition type", ret->name)); + return ret; +} + +/** + * fdisk_parttype_get_string: + * @t: type + * + * Returns: partition type string (e.g. GUID for GPT) + */ +const char *fdisk_parttype_get_string(const struct fdisk_parttype *t) +{ + assert(t); + return t->typestr && *t->typestr ? t->typestr : NULL; +} + +/** + * fdisk_parttype_get_code: + * @t: type + * + * Returns: partition type code (e.g. for MBR) + */ +unsigned int fdisk_parttype_get_code(const struct fdisk_parttype *t) +{ + assert(t); + return t->code; +} + +/** + * fdisk_parttype_get_name: + * @t: type + * + * Returns: partition type human readable name + */ +const char *fdisk_parttype_get_name(const struct fdisk_parttype *t) +{ + assert(t); + return t->name; +} + +/** + * fdisk_parttype_is_unknown: + * @t: type + * + * Checks for example result from fdisk_label_parse_parttype(). + * + * Returns: 1 is type is "unknonw" or 0. + */ +int fdisk_parttype_is_unknown(const struct fdisk_parttype *t) +{ + return t && (t->flags & FDISK_PARTTYPE_UNKNOWN) ? 1 : 0; +} diff --git a/libblkid/libfdisk/src/script.c b/libblkid/libfdisk/src/script.c new file mode 100644 index 000000000..83bda995d --- /dev/null +++ b/libblkid/libfdisk/src/script.c @@ -0,0 +1,1235 @@ + +#include "fdiskP.h" +#include "strutils.h" + +/** + * SECTION: script + * @title: Script + * @short_description: text based sfdisk compatible description of partition table + * + * The libfdisk scripts are based on original sfdisk script (dumps). Each + * script has two parts: script headers and partition table entries + * (partitions). + * + * For more details about script format see sfdisk man page. + */ + +/* script header (e.g. unit: sectors) */ +struct fdisk_scriptheader { + struct list_head headers; + char *name; + char *data; +}; + +/* script control struct */ +struct fdisk_script { + struct fdisk_table *table; + struct list_head headers; + struct fdisk_context *cxt; + + int refcount; + + /* parser's state */ + size_t nlines; + int fmt; /* input format */ + struct fdisk_label *label; +}; + + +static void fdisk_script_free_header(struct fdisk_script *dp, struct fdisk_scriptheader *fi) +{ + if (!fi) + return; + + DBG(SCRIPT, ul_debugobj(fi, "free header %s", fi->name)); + free(fi->name); + free(fi->data); + list_del(&fi->headers); + free(fi); +} + +/** + * fdisk_new_script: + * @cxt: context + * + * The script hold fdisk_table and additional information to read/write + * script to the file. + * + * Returns: newly allocated script struct. + */ +struct fdisk_script *fdisk_new_script(struct fdisk_context *cxt) +{ + struct fdisk_script *dp = NULL; + + dp = calloc(1, sizeof(*dp)); + if (!dp) + return NULL; + + DBG(SCRIPT, ul_debugobj(dp, "alloc")); + dp->refcount = 1; + dp->cxt = cxt; + fdisk_ref_context(cxt); + + dp->table = fdisk_new_table(); + if (!dp->table) { + fdisk_unref_script(dp); + return NULL; + } + + INIT_LIST_HEAD(&dp->headers); + return dp; +} + +/** + * fdisk_new_script_from_file: + * @cxt: context + * @filename: path to the script file + * + * Allocates a new script and reads script from @filename. + * + * Returns: new script instance or NULL in case of error (check errno for more details). + */ +struct fdisk_script *fdisk_new_script_from_file(struct fdisk_context *cxt, + const char *filename) +{ + int rc; + FILE *f; + struct fdisk_script *dp, *res = NULL; + + assert(cxt); + assert(filename); + + DBG(SCRIPT, ul_debug("opening %s", filename)); + f = fopen(filename, "r"); + if (!f) + return NULL; + + dp = fdisk_new_script(cxt); + if (!dp) + goto done; + + rc = fdisk_script_read_file(dp, f); + if (rc) { + errno = -rc; + goto done; + } + + res = dp; +done: + fclose(f); + if (!res) + fdisk_unref_script(dp); + else + errno = 0; + + return res; +} + +/** + * fdisk_ref_script: + * @dp: script pointer + * + * Incremparts reference counter. + */ +void fdisk_ref_script(struct fdisk_script *dp) +{ + if (dp) + dp->refcount++; +} + +static void fdisk_reset_script(struct fdisk_script *dp) +{ + assert(dp); + + DBG(SCRIPT, ul_debugobj(dp, "reset")); + fdisk_unref_table(dp->table); + dp->table = NULL; + + while (!list_empty(&dp->headers)) { + struct fdisk_scriptheader *fi = list_entry(dp->headers.next, + struct fdisk_scriptheader, headers); + fdisk_script_free_header(dp, fi); + } + INIT_LIST_HEAD(&dp->headers); +} + +/** + * fdisk_unref_script: + * @dp: script pointer + * + * De-incremparts reference counter, on zero the @dp is automatically + * deallocated. + */ +void fdisk_unref_script(struct fdisk_script *dp) +{ + if (!dp) + return; + + dp->refcount--; + if (dp->refcount <= 0) { + fdisk_reset_script(dp); + fdisk_unref_context(dp->cxt); + DBG(SCRIPT, ul_debugobj(dp, "free script")); + free(dp); + } +} + +static struct fdisk_scriptheader *script_get_header(struct fdisk_script *dp, + const char *name) +{ + struct list_head *p; + + list_for_each(p, &dp->headers) { + struct fdisk_scriptheader *fi = list_entry(p, struct fdisk_scriptheader, headers); + + if (strcasecmp(fi->name, name) == 0) + return fi; + } + + return NULL; +} + +/** + * fdisk_script_get_header: + * @dp: script instance + * @name: header name + * + * Returns: pointer to header data or NULL. + */ +const char *fdisk_script_get_header(struct fdisk_script *dp, const char *name) +{ + struct fdisk_scriptheader *fi; + + assert(dp); + assert(name); + + fi = script_get_header(dp, name); + return fi ? fi->data : NULL; +} + + +/** + * fdisk_script_set_header: + * @dp: script instance + * @name: header name + * @data: header data (or NULL) + * + * The headers are used as global options (in script) for whole partition + * table, always one header per line. + * + * If no @data specified then the header is removed. If header does not exist + * and @data specified then a new header added. + * + * Note that libfdisk allows to specify arbitrary custom header, the default + * build-in headers are "unit" and "label", and some label specific headers + * (for example "uuid" and "name" for GPT). + * + * Returns: 0 on success, <0 on error + */ +int fdisk_script_set_header(struct fdisk_script *dp, + const char *name, + const char *data) +{ + struct fdisk_scriptheader *fi; + + assert(dp); + assert(name); + + if (!dp || !name) + return -EINVAL; + + fi = script_get_header(dp, name); + if (!fi && !data) + return 0; /* want to remove header that does not exist, success */ + + if (!data) { + /* no data, remove the header */ + fdisk_script_free_header(dp, fi); + return 0; + } + + if (!fi) { + /* new header */ + fi = calloc(1, sizeof(*fi)); + if (!fi) + return -ENOMEM; + INIT_LIST_HEAD(&fi->headers); + fi->name = strdup(name); + fi->data = strdup(data); + if (!fi->data || !fi->name) { + fdisk_script_free_header(dp, fi); + return -ENOMEM; + } + list_add_tail(&fi->headers, &dp->headers); + } else { + /* update existing */ + char *x = strdup(data); + + if (!x) + return -ENOMEM; + free(fi->data); + fi->data = x; + } + + if (strcmp(name, "label") == 0) + dp->label = NULL; + + return 0; +} + +/** + * fdisk_script_get_table: + * @dp: script + * + * The table (container with partitions) is possible to create by + * fdisk_script_read_context() or fdisk_script_read_file(), otherwise + * this function returns NULL. + * + * Returns: NULL or script. + */ +struct fdisk_table *fdisk_script_get_table(struct fdisk_script *dp) +{ + assert(dp); + return dp ? dp->table : NULL; +} + +static struct fdisk_label *script_get_label(struct fdisk_script *dp) +{ + assert(dp); + assert(dp->cxt); + + if (!dp->label) { + dp->label = fdisk_get_label(dp->cxt, + fdisk_script_get_header(dp, "label")); + DBG(SCRIPT, ul_debugobj(dp, "label '%s'", dp->label ? dp->label->name : "")); + } + return dp->label; +} + +/** + * fdisk_script_get_nlines: + * @dp: script + * + * Returns: number of parsed lines or <0 on error. + */ +int fdisk_script_get_nlines(struct fdisk_script *dp) +{ + assert(dp); + return dp->nlines; +} + +/** + * fdisk_script_read_context: + * @dp: script + * @cxt: context + * + * Reads data from the @cxt context (on disk partition table) into the script. + * If the context is no specified than defaults to context used for fdisk_new_script(). + * + * Return: 0 on success, <0 on error. + */ +int fdisk_script_read_context(struct fdisk_script *dp, struct fdisk_context *cxt) +{ + struct fdisk_label *lb; + int rc; + char *p = NULL; + + assert(dp); + + if (!cxt) + cxt = dp->cxt; + + if (!dp || !cxt) + return -EINVAL; + + DBG(SCRIPT, ul_debugobj(dp, "reading context into script")); + fdisk_reset_script(dp); + + lb = fdisk_get_label(cxt, NULL); + if (!lb) + return -EINVAL; + + /* allocate and fill new table */ + rc = fdisk_get_partitions(cxt, &dp->table); + if (rc) + return rc; + + /* generate headers */ + rc = fdisk_script_set_header(dp, "label", fdisk_label_get_name(lb)); + + if (!rc && fdisk_get_disklabel_id(cxt, &p) == 0 && p) { + rc = fdisk_script_set_header(dp, "label-id", p); + free(p); + } + if (!rc && cxt->dev_path) + rc = fdisk_script_set_header(dp, "device", cxt->dev_path); + if (!rc) + rc = fdisk_script_set_header(dp, "unit", "sectors"); + + /* TODO: label specific headers (e.g. uuid for GPT) */ + + DBG(SCRIPT, ul_debugobj(dp, "read context done [rc=%d]", rc)); + return rc; +} + +/** + * fdisk_script_write_file: + * @dp: script + * @f: output file + * + * Writes script @dp to the ile @f. + * + * Returns: 0 on success, <0 on error. + */ +int fdisk_script_write_file(struct fdisk_script *dp, FILE *f) +{ + struct list_head *h; + struct fdisk_partition *pa; + struct fdisk_iter itr; + const char *devname = NULL; + + assert(dp); + assert(f); + + DBG(SCRIPT, ul_debugobj(dp, "writing script to file")); + + /* script headers */ + list_for_each(h, &dp->headers) { + struct fdisk_scriptheader *fi = list_entry(h, struct fdisk_scriptheader, headers); + fprintf(f, "%s: %s\n", fi->name, fi->data); + if (strcmp(fi->name, "device") == 0) + devname = fi->data; + } + + if (!dp->table) { + DBG(SCRIPT, ul_debugobj(dp, "script table empty")); + return 0; + } + + DBG(SCRIPT, ul_debugobj(dp, "%zu entries", fdisk_table_get_nents(dp->table))); + + fputc('\n', f); + + fdisk_reset_iter(&itr, FDISK_ITER_FORWARD); + while (fdisk_table_next_partition(dp->table, &itr, &pa) == 0) { + char *p = NULL; + + if (devname) + p = fdisk_partname(devname, pa->partno + 1); + if (p) { + DBG(SCRIPT, ul_debugobj(dp, "write %s entry", p)); + fprintf(f, "%s :", p); + } else + fprintf(f, "%zu :", pa->partno + 1); + + if (fdisk_partition_has_start(pa)) + fprintf(f, " start=%12ju", pa->start); + if (fdisk_partition_has_size(pa)) + fprintf(f, ", size=%12ju", pa->size); + + if (pa->type && fdisk_parttype_get_string(pa->type)) + fprintf(f, ", type=%s", fdisk_parttype_get_string(pa->type)); + else if (pa->type) + fprintf(f, ", type=%x", fdisk_parttype_get_code(pa->type)); + + if (pa->uuid) + fprintf(f, ", uuid=%s", pa->uuid); + if (pa->name && *pa->name) + fprintf(f, ", name=\"%s\"", pa->name); + + /* for MBR attr=80 means bootable */ + if (pa->attrs) { + struct fdisk_label *lb = script_get_label(dp); + + if (!lb || fdisk_label_get_type(lb) != FDISK_DISKLABEL_DOS) + fprintf(f, ", attrs=\"%s\"", pa->attrs); + } + if (fdisk_partition_is_bootable(pa)) + fprintf(f, ", bootable"); + fputc('\n', f); + } + + DBG(SCRIPT, ul_debugobj(dp, "write script done")); + return 0; +} + +static inline int is_header_line(const char *s) +{ + const char *p = strchr(s, ':'); + + if (!p || p == s || !*(p + 1) || strchr(s, '=')) + return 0; + + return 1; +} + +/* parses "<name>: value", note modifies @s*/ +static int parse_header_line(struct fdisk_script *dp, char *s) +{ + int rc = -EINVAL; + char *name, *value; + + DBG(SCRIPT, ul_debugobj(dp, " parse header '%s'", s)); + + if (!s || !*s) + return -EINVAL; + + name = s; + value = strchr(s, ':'); + if (!value) + goto done; + *value = '\0'; + value++; + + ltrim_whitespace((unsigned char *) name); + rtrim_whitespace((unsigned char *) name); + ltrim_whitespace((unsigned char *) value); + rtrim_whitespace((unsigned char *) value); + + if (strcmp(name, "label") == 0) { + if (dp->cxt && !fdisk_get_label(dp->cxt, value)) + goto done; /* unknown label name */ + } else if (strcmp(name, "unit") == 0) { + if (strcmp(value, "sectors") != 0) + goto done; /* only "sectors" supported */ + } else if (strcmp(name, "label-id") == 0 + || strcmp(name, "device") == 0) { + ; /* whatever is posssible */ + } else + goto done; /* unknown header */ + + if (*name && *value) + rc = fdisk_script_set_header(dp, name, value); +done: + if (rc) + DBG(SCRIPT, ul_debugobj(dp, "header parse error: " + "[rc=%d, name='%s', value='%s']", + rc, name, value)); + return rc; + +} + +/* returns zero terminated string with next token and @str is updated */ +static char *next_token(char **str) +{ + char *tk_begin = NULL, + *tk_end = NULL, + *end = NULL, + *p; + int open_quote = 0; + + for (p = *str; p && *p; p++) { + if (!tk_begin) { + if (isblank(*p)) + continue; + tk_begin = *p == '"' ? p + 1 : p; + } + if (*p == '"') + open_quote ^= 1; + if (open_quote) + continue; + if (isblank(*p) || *p == ',' || *p == ';' || *p == '"' ) + tk_end = p; + else if (*(p + 1) == '\0') + tk_end = p + 1; + if (tk_begin && tk_end) + break; + } + + if (!tk_end) + return NULL; + end = isblank(*tk_end) ? (char *) skip_blank(tk_end) : tk_end; + if (*end == ',' || *end == ';') + end++; + + *tk_end = '\0'; + *str = end; + return tk_begin; +} + +static int next_number(char **s, uint64_t *num, int *power) +{ + char *tk; + int rc = -EINVAL; + + assert(num); + assert(s); + + tk = next_token(s); + if (tk) + rc = parse_size(tk, (uintmax_t *) num, power); + return rc; +} + +static int next_string(char **s, char **str) +{ + char *tk; + int rc = -EINVAL; + + assert(s); + assert(str); + + tk = next_token(s); + if (tk) { + *str = strdup(tk); + rc = !*str ? -ENOMEM : 0; + } + return rc; +} + +static int partno_from_devname(char *s) +{ + int pno; + size_t sz; + char *end, *p; + + sz = rtrim_whitespace((unsigned char *)s); + p = s + sz - 1; + + while (p > s && isdigit(*(p - 1))) + p--; + + errno = 0; + pno = strtol(p, &end, 10); + if (errno || !end || p == end) + return -1; + return pno - 1; +} + +/* dump format + * <device>: start=<num>, size=<num>, type=<string>, ... + */ +static int parse_script_line(struct fdisk_script *dp, char *s) +{ + char *p, *x; + struct fdisk_partition *pa; + int rc = 0; + uint64_t num; + int pno; + + assert(dp); + assert(s); + + DBG(SCRIPT, ul_debugobj(dp, " parse script line: '%s'", s)); + + pa = fdisk_new_partition(); + if (!pa) + return -ENOMEM; + + fdisk_partition_start_follow_default(pa, 1); + fdisk_partition_end_follow_default(pa, 1); + fdisk_partition_partno_follow_default(pa, 1); + + /* set partno */ + p = strchr(s, ':'); + x = strchr(s, '='); + if (p && (!x || p < x)) { + *p = '\0'; + p++; + + pno = partno_from_devname(s); + if (pno >= 0) { + fdisk_partition_partno_follow_default(pa, 0); + fdisk_partition_set_partno(pa, pno); + } + } else + p = s; + + while (rc == 0 && p && *p) { + + DBG(SCRIPT, ul_debugobj(dp, " parsing '%s'", p)); + p = (char *) skip_blank(p); + + if (!strncasecmp(p, "start=", 6)) { + p += 6; + rc = next_number(&p, &num, NULL); + if (!rc) { + fdisk_partition_set_start(pa, num); + fdisk_partition_start_follow_default(pa, 0); + } + } else if (!strncasecmp(p, "size=", 5)) { + int pow = 0; + + p += 5; + rc = next_number(&p, &num, &pow); + if (!rc) { + if (pow) /* specified as <num><suffix> */ + num /= dp->cxt->sector_size; + else /* specified as number of sectors */ + fdisk_partition_size_explicit(pa, 1); + fdisk_partition_set_size(pa, num); + fdisk_partition_end_follow_default(pa, 0); + } + + } else if (!strncasecmp(p, "bootable", 8)) { + char *tk = next_token(&p); + if (strcmp(tk, "bootable") == 0) + pa->boot = 1; + else + rc = -EINVAL; + + } else if (!strncasecmp(p, "attrs=", 6)) { + p += 6; + rc = next_string(&p, &pa->attrs); + + } else if (!strncasecmp(p, "uuid=", 5)) { + p += 5; + rc = next_string(&p, &pa->uuid); + + } else if (!strncasecmp(p, "name=", 5)) { + p += 5; + rc = next_string(&p, &pa->name); + + } else if (!strncasecmp(p, "type=", 5) || + + !strncasecmp(p, "Id=", 3)) { /* backward compatiility */ + char *type; + + p += (*p == 'I' ? 3 : 5); /* "Id=" or "type=" */ + + rc = next_string(&p, &type); + if (rc) + break; + pa->type = fdisk_label_parse_parttype( + script_get_label(dp), type); + free(type); + + if (!pa->type || fdisk_parttype_is_unknown(pa->type)) { + rc = -EINVAL; + fdisk_unref_parttype(pa->type); + pa->type = NULL; + break; + } + + } else { + DBG(SCRIPT, ul_debugobj(dp, "script parse error: unknown field '%s'", p)); + rc = -EINVAL; + break; + } + } + + if (!rc) + rc = fdisk_table_add_partition(dp->table, pa); + if (rc) + DBG(SCRIPT, ul_debugobj(dp, "script parse error: [rc=%d]", rc)); + + fdisk_unref_partition(pa); + return rc; +} + +/* original sfdisk supports partition types shortcuts like 'L' = Linux native + */ +static struct fdisk_parttype *translate_type_shortcuts(struct fdisk_script *dp, char *str) +{ + struct fdisk_label *lb; + const char *type = NULL; + + if (strlen(str) != 1) + return NULL; + + lb = script_get_label(dp); + if (!lb) + return NULL; + + if (lb->id == FDISK_DISKLABEL_DOS) { + switch (*str) { + case 'L': /* Linux */ + type = "83"; + break; + case 'S': /* Swap */ + type = "82"; + break; + case 'E': /* Dos extended */ + type = "05"; + break; + case 'X': /* Linux extended */ + type = "85"; + break; + } + } else if (lb->id == FDISK_DISKLABEL_GPT) { + switch (*str) { + case 'L': /* Linux */ + type = "0FC63DAF-8483-4772-8E79-3D69D8477DE4"; + break; + case 'S': /* Swap */ + type = "0657FD6D-A4AB-43C4-84E5-0933C84B4F4F"; + break; + case 'H': /* Home */ + type = "933AC7E1-2EB4-4F13-B844-0E14E2AEF915"; + break; + } + } + + return type ? fdisk_label_parse_parttype(lb, type) : NULL; +} + +/* simple format: + * <start>, <size>, <type>, <bootable>, ... + */ +static int parse_commas_line(struct fdisk_script *dp, char *s) +{ + int rc = 0; + char *p = s, *str; + struct fdisk_partition *pa; + enum { ITEM_START, ITEM_SIZE, ITEM_TYPE, ITEM_BOOTABLE }; + int item = -1; + + assert(dp); + assert(s); + + pa = fdisk_new_partition(); + if (!pa) + return -ENOMEM; + + fdisk_partition_start_follow_default(pa, 1); + fdisk_partition_end_follow_default(pa, 1); + fdisk_partition_partno_follow_default(pa, 1); + + while (rc == 0 && p && *p) { + uint64_t num; + char *begin; + + p = (char *) skip_blank(p); + item++; + + DBG(SCRIPT, ul_debugobj(dp, " parsing item %d ('%s')", item, p)); + begin = p; + + switch (item) { + case ITEM_START: + if (*p == ',' || *p == ';') + fdisk_partition_start_follow_default(pa, 1); + else { + rc = next_number(&p, &num, NULL); + if (!rc) + fdisk_partition_set_start(pa, num); + fdisk_partition_start_follow_default(pa, 0); + } + break; + case ITEM_SIZE: + if (*p == ',' || *p == ';' || *p == '+') + fdisk_partition_end_follow_default(pa, 1); + else { + int pow = 0; + rc = next_number(&p, &num, &pow); + if (!rc) { + if (pow) /* specified as <size><suffix> */ + num /= dp->cxt->sector_size; + else /* specified as number of sectors */ + fdisk_partition_size_explicit(pa, 1); + fdisk_partition_set_size(pa, num); + } + fdisk_partition_end_follow_default(pa, 0); + } + break; + case ITEM_TYPE: + if (*p == ',' || *p == ';') + break; /* use default type */ + + rc = next_string(&p, &str); + if (rc) + break; + + pa->type = translate_type_shortcuts(dp, str); + if (!pa->type) + pa->type = fdisk_label_parse_parttype( + script_get_label(dp), str); + free(str); + + if (!pa->type || fdisk_parttype_is_unknown(pa->type)) { + rc = -EINVAL; + fdisk_unref_parttype(pa->type); + pa->type = NULL; + break; + } + break; + case ITEM_BOOTABLE: + if (*p == ',' || *p == ';') + break; + else { + char *tk = next_token(&p); + if (tk && *tk == '*' && *(tk + 1) == '\0') + pa->boot = 1; + else if (tk && *tk == '-' && *(tk + 1) == '\0') + pa->boot = 0; + else + rc = -EINVAL; + } + break; + default: + break; + } + + if (begin == p) + p++; + } + + if (!rc) + rc = fdisk_table_add_partition(dp->table, pa); + if (rc) + DBG(SCRIPT, ul_debugobj(dp, "script parse error: [rc=%d]", rc)); + + fdisk_unref_partition(pa); + return rc; +} + +/* modifies @s ! */ +int fdisk_script_read_buffer(struct fdisk_script *dp, char *s) +{ + int rc = 0; + + assert(dp); + assert(s); + + DBG(SCRIPT, ul_debugobj(dp, " parsing buffer")); + + s = (char *) skip_blank(s); + if (!s || !*s) + return 0; /* nothing baby, ignore */ + + if (!dp->table) { + dp->table = fdisk_new_table(); + if (!dp->table) + return -ENOMEM; + } + + /* parse header lines only if no partition specified yet */ + if (fdisk_table_is_empty(dp->table) && is_header_line(s)) + rc = parse_header_line(dp, s); + + /* parse script format */ + else if (strchr(s, '=')) + rc = parse_script_line(dp, s); + + /* parse simple <value>, ... format */ + else + rc = parse_commas_line(dp, s); + + if (rc) + DBG(SCRIPT, ul_debugobj(dp, "%zu: parse error [rc=%d]", + dp->nlines, rc)); + return rc; +} + +/** + * fdisk_script_read_line: + * @dp: script + * @f: file + * @buf: buffer to store one line of the file + * @bufsz: buffer size + * + * Reads next line into dump. + * + * Returns: 0 on success, <0 on error, 1 when nothing to read. + */ +int fdisk_script_read_line(struct fdisk_script *dp, FILE *f, char *buf, size_t bufsz) +{ + char *s; + + assert(dp); + assert(f); + + DBG(SCRIPT, ul_debugobj(dp, " parsing line %zu", dp->nlines)); + + /* read the next non-blank non-comment line */ + do { + if (fgets(buf, bufsz, f) == NULL) + return 1; + dp->nlines++; + s = strchr(buf, '\n'); + if (!s) { + /* Missing final newline? Otherwise an extremely */ + /* long line - assume file was corrupted */ + if (feof(f)) { + DBG(SCRIPT, ul_debugobj(dp, "no final newline")); + s = strchr(buf, '\0'); + } else { + DBG(SCRIPT, ul_debugobj(dp, + "%zu: missing newline at line", dp->nlines)); + return -EINVAL; + } + } + + *s = '\0'; + if (--s >= buf && *s == '\r') + *s = '\0'; + s = (char *) skip_blank(buf); + } while (*s == '\0' || *s == '#'); + + return fdisk_script_read_buffer(dp, s); +} + + +/** + * fdisk_script_read_file: + * @dp: script + * @f: input file + * + * Reads file @f into script @dp. + * + * Returns: 0 on success, <0 on error. + */ +int fdisk_script_read_file(struct fdisk_script *dp, FILE *f) +{ + char buf[BUFSIZ]; + int rc; + + assert(dp); + assert(f); + + DBG(SCRIPT, ul_debugobj(dp, "parsing file")); + + while (!feof(f)) { + rc = fdisk_script_read_line(dp, f, buf, sizeof(buf)); + if (rc) + break; + } + + if (rc == 1) + rc = 0; /* end of file */ + + DBG(SCRIPT, ul_debugobj(dp, "parsing file done [rc=%d]", rc)); + return rc; +} + +/** + * fdisk_set_script: + * @cxt: context + * @dp: script (or NULL to remove previous reference) + * + * Sets reference to the @dp script. The script headers might be used by label + * drivers to overwrite built-in defaults (for example disk label Id) and label + * driver might optimize the default semantic to be more usable for scripts + * (for example to not ask for primary/logical/extended partition type). + * + * Note that script also contains reference to the fdisk context (see + * fdisk_new_script()). This context may be completely independent on + * context used for fdisk_set_script(). + * + * Returns: <0 on error, 0 on success. + */ +int fdisk_set_script(struct fdisk_context *cxt, struct fdisk_script *dp) +{ + assert(cxt); + + /* unref old */ + if (cxt->script) + fdisk_unref_script(cxt->script); + + /* ref new */ + cxt->script = dp; + if (cxt->script) { + DBG(CXT, ul_debugobj(cxt, "setting reference to script %p", cxt->script)); + fdisk_ref_script(cxt->script); + } + + return 0; +} + +/** + * fdisk_get_script: + * @cxt: context + * + * Returns: the current script or NULL. + */ +struct fdisk_script *fdisk_get_script(struct fdisk_context *cxt) +{ + assert(cxt); + return cxt->script; +} + +/** + * fdisk_apply_script_headers: + * @cxt: context + * @dp: script + * + * Associte context @cxt with script @dp and creates a new empty disklabel. + * + * Returns: 0 on success, <0 on error. + */ +int fdisk_apply_script_headers(struct fdisk_context *cxt, struct fdisk_script *dp) +{ + const char *name; + + assert(cxt); + assert(dp); + + DBG(SCRIPT, ul_debugobj(dp, "applying script headers")); + fdisk_set_script(cxt, dp); + + /* create empty label */ + name = fdisk_script_get_header(dp, "label"); + if (!name) + return -EINVAL; + + return fdisk_create_disklabel(cxt, name); +} + +/** + * fdisk_apply_script: + * @cxt: context + * @dp: script + * + * This function creates a new disklabel and partition within context @cxt. You + * have to call fdisk_write_disklabel() to apply changes to the device. + * + * Returns: 0 on error, <0 on error. + */ +int fdisk_apply_script(struct fdisk_context *cxt, struct fdisk_script *dp) +{ + int rc; + struct fdisk_script *old; + + assert(dp); + assert(cxt); + + DBG(CXT, ul_debugobj(cxt, "applying script %p", dp)); + + old = fdisk_get_script(cxt); + + /* create empty disk label */ + rc = fdisk_apply_script_headers(cxt, dp); + + /* create partitions */ + if (!rc && dp->table) + rc = fdisk_apply_table(cxt, dp->table); + + fdisk_set_script(cxt, old); + DBG(CXT, ul_debugobj(cxt, "script done [rc=%d]", rc)); + return rc; +} + +#ifdef TEST_PROGRAM +int test_dump(struct fdisk_test *ts, int argc, char *argv[]) +{ + char *devname = argv[1]; + struct fdisk_context *cxt; + struct fdisk_script *dp; + + cxt = fdisk_new_context(); + fdisk_assign_device(cxt, devname, 1); + + dp = fdisk_new_script(cxt); + fdisk_script_read_context(dp, NULL); + + fdisk_script_write_file(dp, stdout); + fdisk_unref_script(dp); + fdisk_unref_context(cxt); + + return 0; +} + +int test_read(struct fdisk_test *ts, int argc, char *argv[]) +{ + char *filename = argv[1]; + struct fdisk_script *dp; + struct fdisk_context *cxt; + FILE *f; + + if (!(f = fopen(filename, "r"))) + err(EXIT_FAILURE, "%s: cannot open", filename); + + cxt = fdisk_new_context(); + dp = fdisk_new_script(cxt); + + fdisk_script_read_file(dp, f); + fclose(f); + + fdisk_script_write_file(dp, stdout); + fdisk_unref_script(dp); + fdisk_unref_context(cxt); + + return 0; +} + +int test_stdin(struct fdisk_test *ts, int argc, char *argv[]) +{ + char buf[BUFSIZ]; + struct fdisk_script *dp; + struct fdisk_context *cxt; + int rc = 0; + + cxt = fdisk_new_context(); + dp = fdisk_new_script(cxt); + fdisk_script_set_header(dp, "label", "dos"); + + printf("<start>, <size>, <type>, <bootable: *|->\n"); + do { + struct fdisk_partition *pa; + size_t n = fdisk_table_get_nents(dp->table); + + printf(" #%zu :\n", n + 1); + rc = fdisk_script_read_line(dp, stdin, buf, sizeof(buf)); + + if (rc == 0) { + pa = fdisk_table_get_partition(dp->table, n); + printf(" #%zu %12ju %12ju\n", n + 1, + fdisk_partition_get_start(pa), + fdisk_partition_get_size(pa)); + } + } while (rc == 0); + + if (!rc) + fdisk_script_write_file(dp, stdout); + fdisk_unref_script(dp); + fdisk_unref_context(cxt); + + return rc; +} + +int test_apply(struct fdisk_test *ts, int argc, char *argv[]) +{ + char *devname = argv[1], *scriptname = argv[2]; + struct fdisk_context *cxt; + struct fdisk_script *dp = NULL; + struct fdisk_table *tb = NULL; + struct fdisk_iter *itr = NULL; + struct fdisk_partition *pa = NULL; + int rc; + + cxt = fdisk_new_context(); + fdisk_assign_device(cxt, devname, 0); + + dp = fdisk_new_script_from_file(cxt, scriptname); + if (!dp) + return -errno; + + rc = fdisk_apply_script(cxt, dp); + if (rc) + goto done; + fdisk_unref_script(dp); + + /* list result */ + fdisk_list_disklabel(cxt); + fdisk_get_partitions(cxt, &tb); + + itr = fdisk_new_iter(FDISK_ITER_FORWARD); + while (fdisk_table_next_partition(tb, itr, &pa) == 0) { + printf(" #%zu %12ju %12ju\n", fdisk_partition_get_partno(pa), + fdisk_partition_get_start(pa), + fdisk_partition_get_size(pa)); + } + +done: + fdisk_free_iter(itr); + fdisk_unref_table(tb); + + /*fdisk_write_disklabel(cxt);*/ + fdisk_unref_context(cxt); + return 0; +} + +int main(int argc, char *argv[]) +{ + struct fdisk_test tss[] = { + { "--dump", test_dump, "<device> dump PT as script" }, + { "--read", test_read, "<file> read PT script from file" }, + { "--apply", test_apply, "<device> <file> try apply script from file to device" }, + { "--stdin", test_stdin, " read input like sfdisk" }, + { NULL } + }; + + return fdisk_run_test(tss, argc, argv); +} + +#endif diff --git a/libblkid/libfdisk/src/sgi.c b/libblkid/libfdisk/src/sgi.c new file mode 100644 index 000000000..cd4cedff0 --- /dev/null +++ b/libblkid/libfdisk/src/sgi.c @@ -0,0 +1,1185 @@ +/* + * + * Copyright (C) 2012 Davidlohr Bueso <dave@gnu.org> + * 2013 Karel Zak <kzak@redhat.com> + * + * This is a re-written version for libfdisk, the original was fdisksgilabel.c + * from util-linux fdisk, by: + * + * Andreas Neuper, Sep 1998, + * Arnaldo Carvalho de Melo <acme@conectiva.com.br>, Mar 1999, + * Phillip Kesling <pkesling@sgi.com>, Mar 2003. + */ + +#include "c.h" +#include "nls.h" +#include "all-io.h" + +#include "blkdev.h" + +#include "bitops.h" +#include "pt-sgi.h" +#include "pt-mbr.h" +#include "fdiskP.h" + +/** + * SECTION: sgi + * @title: SGI + * @short_description: disk label specific functions + * + */ + +/* + * in-memory fdisk SGI stuff + */ +struct fdisk_sgi_label { + struct fdisk_label head; /* generic fdisk part */ + struct sgi_disklabel *header; /* on-disk data (pointer to cxt->firstsector) */ + + struct sgi_freeblocks { + unsigned int first; + unsigned int last; + } freelist[SGI_MAXPARTITIONS + 1]; +}; + +static struct fdisk_parttype sgi_parttypes[] = +{ + {SGI_TYPE_VOLHDR, N_("SGI volhdr")}, + {SGI_TYPE_TRKREPL, N_("SGI trkrepl")}, + {SGI_TYPE_SECREPL, N_("SGI secrepl")}, + {SGI_TYPE_SWAP, N_("SGI raw")}, + {SGI_TYPE_BSD, N_("SGI bsd")}, + {SGI_TYPE_SYSV, N_("SGI sysv")}, + {SGI_TYPE_ENTIRE_DISK, N_("SGI volume")}, + {SGI_TYPE_EFS, N_("SGI efs")}, + {SGI_TYPE_LVOL, N_("SGI lvol")}, + {SGI_TYPE_RLVOL, N_("SGI rlvol")}, + {SGI_TYPE_XFS, N_("SGI xfs")}, + {SGI_TYPE_XFSLOG, N_("SGI xfslog")}, + {SGI_TYPE_XLV, N_("SGI xlv")}, + {SGI_TYPE_XVM, N_("SGI xvm")}, + {MBR_LINUX_SWAP_PARTITION, N_("Linux swap")}, + {MBR_LINUX_DATA_PARTITION, N_("Linux native")}, + {MBR_LINUX_LVM_PARTITION, N_("Linux LVM")}, + {MBR_LINUX_RAID_PARTITION, N_("Linux RAID")}, + {0, NULL } +}; + +static unsigned int sgi_get_start_sector(struct fdisk_context *cxt, int i ); +static unsigned int sgi_get_num_sectors(struct fdisk_context *cxt, int i ); +static int sgi_get_bootpartition(struct fdisk_context *cxt); +static int sgi_get_swappartition(struct fdisk_context *cxt); + +/* Returns a pointer buffer with on-disk data. */ +static inline struct sgi_disklabel *self_disklabel(struct fdisk_context *cxt) +{ + assert(cxt); + assert(cxt->label); + assert(fdisk_is_label(cxt, SGI)); + + return ((struct fdisk_sgi_label *) cxt->label)->header; +} + +/* Returns in-memory fdisk data. */ +static inline struct fdisk_sgi_label *self_label(struct fdisk_context *cxt) +{ + assert(cxt); + assert(cxt->label); + assert(fdisk_is_label(cxt, SGI)); + + return (struct fdisk_sgi_label *) cxt->label; +} + +/* + * Information within second on-disk block + */ +#define SGI_INFO_MAGIC 0x00072959 + +struct sgi_info { + unsigned int magic; /* looks like a magic number */ + unsigned int a2; + unsigned int a3; + unsigned int a4; + unsigned int b1; + unsigned short b2; + unsigned short b3; + unsigned int c[16]; + unsigned short d[3]; + unsigned char scsi_string[50]; + unsigned char serial[137]; + unsigned short check1816; + unsigned char installer[225]; +}; + +static struct sgi_info *sgi_new_info(void) +{ + struct sgi_info *info = calloc(1, sizeof(struct sgi_info)); + + if (!info) + return NULL; + + info->magic = cpu_to_be32(SGI_INFO_MAGIC); + info->b1 = cpu_to_be32(-1); + info->b2 = cpu_to_be16(-1); + info->b3 = cpu_to_be16(1); + + /* You may want to replace this string !!!!!!! */ + strcpy((char *) info->scsi_string, "IBM OEM 0662S12 3 30"); + strcpy((char *) info->serial, "0000"); + info->check1816 = cpu_to_be16(18 * 256 + 16); + strcpy((char *) info->installer, "Sfx version 5.3, Oct 18, 1994"); + + return info; +} + +static void sgi_free_info(struct sgi_info *info) +{ + free(info); +} + +/** + * fdisk_sgi_create_info: + * @cxt: context + * + * This function add hint about SGI label (e.g. set "sgilabel" as volume name) + * to the first SGI volume. This is probably old SGI convention without any + * effect to the device partitioning. + * + * Returns: 0 on success, <0 on error + */ +int fdisk_sgi_create_info(struct fdisk_context *cxt) +{ + struct sgi_disklabel *sgilabel = self_disklabel(cxt); + + /* I keep SGI's habit to write the sgilabel to the second block */ + sgilabel->volume[0].block_num = cpu_to_be32(2); + sgilabel->volume[0].num_bytes = cpu_to_be32(sizeof(struct sgi_info)); + strncpy((char *) sgilabel->volume[0].name, "sgilabel", 8); + + fdisk_info(cxt, _("SGI info created on second sector.")); + return 0; +} + + +/* + * only dealing with free blocks here + */ +static void set_freelist(struct fdisk_context *cxt, + size_t i, unsigned int f, unsigned int l) +{ + struct fdisk_sgi_label *sgi = self_label(cxt); + + if (i < ARRAY_SIZE(sgi->freelist)) { + sgi->freelist[i].first = f; + sgi->freelist[i].last = l; + } +} + +static void add_to_freelist(struct fdisk_context *cxt, + unsigned int f, unsigned int l) +{ + struct fdisk_sgi_label *sgi = self_label(cxt); + size_t i; + + for (i = 0; i < ARRAY_SIZE(sgi->freelist); i++) { + if (sgi->freelist[i].last == 0) + break; + } + set_freelist(cxt, i, f, l); +} + +static void clear_freelist(struct fdisk_context *cxt) +{ + struct fdisk_sgi_label *sgi = self_label(cxt); + + memset(sgi->freelist, 0, sizeof(sgi->freelist)); +} + +static unsigned int is_in_freelist(struct fdisk_context *cxt, unsigned int b) +{ + struct fdisk_sgi_label *sgi = self_label(cxt); + size_t i; + + for (i = 0; i < ARRAY_SIZE(sgi->freelist); i++) { + if (sgi->freelist[i].first <= b + && sgi->freelist[i].last >= b) + return sgi->freelist[i].last; + } + + return 0; +} + + +static int sgi_get_nsect(struct fdisk_context *cxt) +{ + struct sgi_disklabel *sgilabel = self_disklabel(cxt); + return be16_to_cpu(sgilabel->devparam.nsect); +} + +static int sgi_get_ntrks(struct fdisk_context *cxt) +{ + struct sgi_disklabel *sgilabel = self_disklabel(cxt); + return be16_to_cpu(sgilabel->devparam.ntrks); +} + +static size_t count_used_partitions(struct fdisk_context *cxt) +{ + size_t i, ct = 0; + + for (i = 0; i < cxt->label->nparts_max; i++) + ct += sgi_get_num_sectors(cxt, i) > 0; + + return ct; +} + +static int sgi_probe_label(struct fdisk_context *cxt) +{ + struct fdisk_sgi_label *sgi; + struct sgi_disklabel *sgilabel; + + assert(cxt); + assert(cxt->label); + assert(fdisk_is_label(cxt, SGI)); + assert(sizeof(struct sgi_disklabel) <= 512); + + /* map first sector to header */ + sgi = (struct fdisk_sgi_label *) cxt->label; + sgi->header = (struct sgi_disklabel *) cxt->firstsector; + sgilabel = sgi->header; + + if (be32_to_cpu(sgilabel->magic) != SGI_LABEL_MAGIC) { + sgi->header = NULL; + return 0; + } + + /* + * test for correct checksum + */ + if (sgi_pt_checksum(sgilabel) != 0) + fdisk_warnx(cxt, _("Detected an SGI disklabel with wrong checksum.")); + + clear_freelist(cxt); + cxt->label->nparts_max = SGI_MAXPARTITIONS; + cxt->label->nparts_cur = count_used_partitions(cxt); + return 1; +} + +static int sgi_list_table(struct fdisk_context *cxt) +{ + struct sgi_disklabel *sgilabel = self_disklabel(cxt); + struct sgi_device_parameter *sgiparam = &sgilabel->devparam; + int rc = 0; + + if (fdisk_is_details(cxt)) + fdisk_info(cxt, _( + "Label geometry: %d heads, %llu sectors\n" + " %llu cylinders, %d physical cylinders\n" + " %d extra sects/cyl, interleave %d:1\n"), + cxt->geom.heads, cxt->geom.sectors, + cxt->geom.cylinders, be16_to_cpu(sgiparam->pcylcount), + (int) sgiparam->sparecyl, be16_to_cpu(sgiparam->ilfact)); + + fdisk_info(cxt, _("Bootfile: %s"), sgilabel->boot_file); + return rc; +} + +static unsigned int sgi_get_start_sector(struct fdisk_context *cxt, int i) +{ + struct sgi_disklabel *sgilabel = self_disklabel(cxt); + return be32_to_cpu(sgilabel->partitions[i].first_block); +} + +static unsigned int sgi_get_num_sectors(struct fdisk_context *cxt, int i) +{ + struct sgi_disklabel *sgilabel = self_disklabel(cxt); + return be32_to_cpu(sgilabel->partitions[i].num_blocks); +} + +static int sgi_get_sysid(struct fdisk_context *cxt, int i) +{ + struct sgi_disklabel *sgilabel = self_disklabel(cxt); + return be32_to_cpu(sgilabel->partitions[i].type); +} + +static int sgi_get_bootpartition(struct fdisk_context *cxt) +{ + struct sgi_disklabel *sgilabel = self_disklabel(cxt); + return be16_to_cpu(sgilabel->root_part_num); +} + +static int sgi_get_swappartition(struct fdisk_context *cxt) +{ + struct sgi_disklabel *sgilabel = self_disklabel(cxt); + return be16_to_cpu(sgilabel->swap_part_num); +} + +static unsigned int sgi_get_lastblock(struct fdisk_context *cxt) +{ + return cxt->geom.heads * cxt->geom.sectors * cxt->geom.cylinders; +} + +static struct fdisk_parttype *sgi_get_parttype(struct fdisk_context *cxt, size_t n) +{ + struct fdisk_parttype *t; + + if (n >= cxt->label->nparts_max) + return NULL; + + t = fdisk_label_get_parttype_from_code(cxt->label, sgi_get_sysid(cxt, n)); + return t ? : fdisk_new_unknown_parttype(sgi_get_sysid(cxt, n), NULL); +} + +/* fdisk_get_partition() backend */ +static int sgi_get_partition(struct fdisk_context *cxt, size_t n, struct fdisk_partition *pa) +{ + fdisk_sector_t start, len; + + pa->used = sgi_get_num_sectors(cxt, n) > 0; + if (!pa->used) + return 0; + + start = sgi_get_start_sector(cxt, n); + len = sgi_get_num_sectors(cxt, n); + + pa->type = sgi_get_parttype(cxt, n); + pa->size = len; + pa->start = start; + + if (pa->type && pa->type->code == SGI_TYPE_ENTIRE_DISK) + pa->wholedisk = 1; + + pa->attrs = sgi_get_swappartition(cxt) == (int) n ? "swap" : + sgi_get_bootpartition(cxt) == (int) n ? "boot" : NULL; + if (pa->attrs) + pa->attrs = strdup(pa->attrs); + + return 0; +} + + +static int sgi_check_bootfile(struct fdisk_context *cxt, const char *name) +{ + size_t sz; + struct sgi_disklabel *sgilabel = self_disklabel(cxt); + + sz = strlen(name); + + if (sz < 3) { + /* "/a\n" is minimum */ + fdisk_warnx(cxt, _("Invalid bootfile! The bootfile must " + "be an absolute non-zero pathname, " + "e.g. \"/unix\" or \"/unix.save\".")); + return -EINVAL; + + } else if (sz > sizeof(sgilabel->boot_file)) { + fdisk_warnx(cxt, P_("Name of bootfile is too long: %zu byte maximum.", + "Name of bootfile is too long: %zu bytes maximum.", + sizeof(sgilabel->boot_file)), + sizeof(sgilabel->boot_file)); + return -EINVAL; + + } else if (*name != '/') { + fdisk_warnx(cxt, _("Bootfile must have a fully qualified pathname.")); + return -EINVAL; + } + + if (strncmp(name, (char *) sgilabel->boot_file, + sizeof(sgilabel->boot_file))) { + fdisk_warnx(cxt, _("Be aware that the bootfile is not checked " + "for existence. SGI's default is \"/unix\", " + "and for backup \"/unix.save\".")); + return 0; /* filename is correct and did change */ + } + + return 1; /* filename did not change */ +} + +/** + * fdisk_sgi_set_bootfile: + * @cxt: context + * + * Allows to set SGI boot file. The function uses Ask API for dialog with + * user. + * + * Returns: 0 on success, <0 on error + */ +int fdisk_sgi_set_bootfile(struct fdisk_context *cxt) +{ + int rc = 0; + size_t sz; + char *name = NULL; + struct sgi_disklabel *sgilabel = self_disklabel(cxt); + + fdisk_info(cxt, _("The current boot file is: %s"), sgilabel->boot_file); + + rc = fdisk_ask_string(cxt, _("Enter of the new boot file"), &name); + if (rc == 0) + rc = sgi_check_bootfile(cxt, name); + if (rc) { + if (rc == 1) + fdisk_info(cxt, _("Boot file is unchanged.")); + goto done; + } + + memset(sgilabel->boot_file, 0, sizeof(sgilabel->boot_file)); + sz = strlen(name); + + assert(sz <= sizeof(sgilabel->boot_file)); /* see sgi_check_bootfile() */ + + memcpy(sgilabel->boot_file, name, sz); + + fdisk_info(cxt, _("Bootfile has been changed to \"%s\"."), name); +done: + free(name); + return rc; +} + +static int sgi_write_disklabel(struct fdisk_context *cxt) +{ + struct sgi_disklabel *sgilabel; + struct sgi_info *info = NULL; + + assert(cxt); + assert(cxt->label); + assert(fdisk_is_label(cxt, SGI)); + + sgilabel = self_disklabel(cxt); + sgilabel->csum = 0; + sgilabel->csum = cpu_to_be32(sgi_pt_checksum(sgilabel)); + + assert(sgi_pt_checksum(sgilabel) == 0); + + if (lseek(cxt->dev_fd, 0, SEEK_SET) < 0) + goto err; + if (write_all(cxt->dev_fd, sgilabel, DEFAULT_SECTOR_SIZE)) + goto err; + if (!strncmp((char *) sgilabel->volume[0].name, "sgilabel", 8)) { + /* + * Keep this habit of first writing the "sgilabel". + * I never tested whether it works without. (AN 1998-10-02) + */ + int infostartblock + = be32_to_cpu(sgilabel->volume[0].block_num); + + if (lseek(cxt->dev_fd, (off_t) infostartblock * + DEFAULT_SECTOR_SIZE, SEEK_SET) < 0) + goto err; + info = sgi_new_info(); + if (!info) + goto err; + if (write_all(cxt->dev_fd, info, sizeof(*info))) + goto err; + } + + sgi_free_info(info); + return 0; +err: + sgi_free_info(info); + return -errno; +} + +static int compare_start(struct fdisk_context *cxt, + const void *x, const void *y) +{ + /* + * Sort according to start sectors and prefer the largest partition: + * entry zero is the entire-disk entry. + */ + unsigned int i = *(int *) x; + unsigned int j = *(int *) y; + unsigned int a = sgi_get_start_sector(cxt, i); + unsigned int b = sgi_get_start_sector(cxt, j); + unsigned int c = sgi_get_num_sectors(cxt, i); + unsigned int d = sgi_get_num_sectors(cxt, j); + + if (a == b) + return (d > c) ? 1 : (d == c) ? 0 : -1; + return (a > b) ? 1 : -1; +} + +static void generic_swap(void *a0, void *b0, int size) +{ + char *a = a0, *b = b0; + + for (; size > 0; --size, a++, b++) { + char t = *a; + *a = *b; + *b = t; + } +} + + +/* heap sort, based on Matt Mackall's linux kernel version */ +static void sort(void *base0, size_t num, size_t size, struct fdisk_context *cxt, + int (*cmp_func)(struct fdisk_context *, const void *, const void *)) +{ + /* pre-scale counters for performance */ + int i = (num/2 - 1) * size; + size_t n = num * size, c, r; + char *base = base0; + + /* heapify */ + for ( ; i >= 0; i -= size) { + for (r = i; r * 2 + size < n; r = c) { + c = r * 2 + size; + if (c < n - size && + cmp_func(cxt, base + c, base + c + size) < 0) + c += size; + if (cmp_func(cxt, base + r, base + c) >= 0) + break; + generic_swap(base + r, base + c, size); + } + } + + /* sort */ + for (i = n - size; i > 0; i -= size) { + generic_swap(base, base + i, size); + for (r = 0; r * 2 + size < (size_t) i; r = c) { + c = r * 2 + size; + if (c < i - size && + cmp_func(cxt, base + c, base + c + size) < 0) + c += size; + if (cmp_func(cxt, base + r, base + c) >= 0) + break; + generic_swap(base + r, base + c, size); + } + } +} + +static int verify_disklabel(struct fdisk_context *cxt, int verbose) +{ + int Index[SGI_MAXPARTITIONS]; /* list of valid partitions */ + int sortcount = 0; /* number of used partitions, i.e. non-zero lengths */ + int entire = 0, i = 0; + unsigned int start = 0; + long long gap = 0; /* count unused blocks */ + unsigned int lastblock = sgi_get_lastblock(cxt); + + assert(cxt); + assert(cxt->label); + assert(fdisk_is_label(cxt, SGI)); + + clear_freelist(cxt); + memset(Index, 0, sizeof(Index)); + + for (i=0; i < SGI_MAXPARTITIONS; i++) { + if (sgi_get_num_sectors(cxt, i) != 0) { + Index[sortcount++] = i; + if (sgi_get_sysid(cxt, i) == SGI_TYPE_ENTIRE_DISK + && entire++ == 1) { + if (verbose) + fdisk_info(cxt, _("More than one entire " + "disk entry present.")); + } + } + } + if (sortcount == 0) { + if (verbose) + fdisk_info(cxt, _("No partitions defined.")); + if (lastblock) + add_to_freelist(cxt, 0, lastblock); + return (lastblock > 0) ? 1 : (lastblock == 0) ? 0 : -1; + } + + sort(Index, sortcount, sizeof(Index[0]), cxt, compare_start); + + if (sgi_get_sysid(cxt, Index[0]) == SGI_TYPE_ENTIRE_DISK) { + if (verbose && Index[0] != 10) + fdisk_info(cxt, _("IRIX likes it when partition 11 " + "covers the entire disk.")); + + if (verbose && sgi_get_start_sector(cxt, Index[0]) != 0) + fdisk_info(cxt, _("The entire disk partition should " + "start at block 0, not at block %d."), + sgi_get_start_sector(cxt, Index[0])); + + if (verbose && sgi_get_num_sectors(cxt, Index[0]) != lastblock) + DBG(LABEL, ul_debug( + "entire disk partition=%ds, but disk=%ds", + sgi_get_num_sectors(cxt, Index[0]), + lastblock)); + lastblock = sgi_get_num_sectors(cxt, Index[0]); + } else if (verbose) { + fdisk_info(cxt, _("Partition 11 should cover the entire disk.")); + DBG(LABEL, ul_debug("sysid=%d\tpartition=%d", + sgi_get_sysid(cxt, Index[0]), Index[0]+1)); + } + for (i=1, start=0; i<sortcount; i++) { + int cylsize = sgi_get_nsect(cxt) * sgi_get_ntrks(cxt); + + if (verbose && cylsize + && (sgi_get_start_sector(cxt, Index[i]) % cylsize) != 0) + DBG(LABEL, ul_debug("partition %d does not start on " + "cylinder boundary.", Index[i]+1)); + + if (verbose && cylsize + && sgi_get_num_sectors(cxt, Index[i]) % cylsize != 0) + DBG(LABEL, ul_debug("partition %d does not end on " + "cylinder boundary.", Index[i]+1)); + + /* We cannot handle several "entire disk" entries. */ + if (sgi_get_sysid(cxt, Index[i]) == SGI_TYPE_ENTIRE_DISK) + continue; + + if (start > sgi_get_start_sector(cxt, Index[i])) { + if (verbose) + fdisk_info(cxt, + P_("Partitions %d and %d overlap by %d sector.", + "Partitions %d and %d overlap by %d sectors.", + start - sgi_get_start_sector(cxt, Index[i])), + Index[i-1]+1, Index[i]+1, + start - sgi_get_start_sector(cxt, Index[i])); + if (gap > 0) gap = -gap; + if (gap == 0) gap = -1; + } + if (start < sgi_get_start_sector(cxt, Index[i])) { + if (verbose) + fdisk_info(cxt, + P_("Unused gap of %8u sector: sector %8u", + "Unused gap of %8u sectors: sectors %8u-%u", + sgi_get_start_sector(cxt, Index[i]) - start), + sgi_get_start_sector(cxt, Index[i]) - start, + start, sgi_get_start_sector(cxt, Index[i])-1); + gap += sgi_get_start_sector(cxt, Index[i]) - start; + add_to_freelist(cxt, start, + sgi_get_start_sector(cxt, Index[i])); + } + start = sgi_get_start_sector(cxt, Index[i]) + + sgi_get_num_sectors(cxt, Index[i]); + /* Align free space on cylinder boundary. */ + if (cylsize && start % cylsize) + start += cylsize - (start % cylsize); + + DBG(LABEL, ul_debug("%2d:%12d\t%12d\t%12d", Index[i], + sgi_get_start_sector(cxt, Index[i]), + sgi_get_num_sectors(cxt, Index[i]), + sgi_get_sysid(cxt, Index[i]))); + } + if (start < lastblock) { + if (verbose) + fdisk_info(cxt, P_("Unused gap of %8u sector: sector %8u", + "Unused gap of %8u sectors: sectors %8u-%u", + lastblock - start), + lastblock - start, start, lastblock-1); + gap += lastblock - start; + add_to_freelist(cxt, start, lastblock); + } + /* + * Done with arithmetics. Go for details now. + */ + if (verbose) { + if (sgi_get_bootpartition(cxt) < 0 + || !sgi_get_num_sectors(cxt, sgi_get_bootpartition(cxt))) + fdisk_info(cxt, _("The boot partition does not exist.")); + + if (sgi_get_swappartition(cxt) < 0 + || !sgi_get_num_sectors(cxt, sgi_get_swappartition(cxt))) + fdisk_info(cxt, _("The swap partition does not exist.")); + + else if (sgi_get_sysid(cxt, sgi_get_swappartition(cxt)) != SGI_TYPE_SWAP + && sgi_get_sysid(cxt, sgi_get_swappartition(cxt)) != MBR_LINUX_SWAP_PARTITION) + fdisk_info(cxt, _("The swap partition has no swap type.")); + + if (sgi_check_bootfile(cxt, "/unix")) + fdisk_info(cxt, _("You have chosen an unusual bootfile name.")); + } + + return (gap > 0) ? 1 : (gap == 0) ? 0 : -1; +} + +static int sgi_verify_disklabel(struct fdisk_context *cxt) +{ + return verify_disklabel(cxt, 1); +} + +static int sgi_gaps(struct fdisk_context *cxt) +{ + /* + * returned value is: + * = 0 : disk is properly filled to the rim + * < 0 : there is an overlap + * > 0 : there is still some vacant space + */ + return verify_disklabel(cxt, 0); +} + +/* Returns partition index of first entry marked as entire disk. */ +static int sgi_entire(struct fdisk_context *cxt) +{ + size_t i; + + for (i = 0; i < SGI_MAXPARTITIONS; i++) + if (sgi_get_sysid(cxt, i) == SGI_TYPE_ENTIRE_DISK) + return i; + return -1; +} + +static int set_partition(struct fdisk_context *cxt, size_t i, + unsigned int start, unsigned int length, int sys) +{ + struct sgi_disklabel *sgilabel; + + assert(cxt); + assert(cxt->label); + assert(fdisk_is_label(cxt, SGI)); + + sgilabel = self_disklabel(cxt); + sgilabel->partitions[i].type = cpu_to_be32(sys); + sgilabel->partitions[i].num_blocks = cpu_to_be32(length); + sgilabel->partitions[i].first_block = cpu_to_be32(start); + + fdisk_label_set_changed(cxt->label, 1); + + if (sgi_gaps(cxt) < 0) /* rebuild freelist */ + fdisk_warnx(cxt, _("Partition overlap on the disk.")); + if (length) { + struct fdisk_parttype *t = + fdisk_label_get_parttype_from_code(cxt->label, sys); + fdisk_info_new_partition(cxt, i + 1, start, start + length, t); + } + + return 0; +} + +static void sgi_set_entire(struct fdisk_context *cxt) +{ + size_t n; + + for (n = 10; n < cxt->label->nparts_max; n++) { + if (!sgi_get_num_sectors(cxt, n)) { + set_partition(cxt, n, 0, sgi_get_lastblock(cxt), SGI_TYPE_ENTIRE_DISK); + break; + } + } +} + +static void sgi_set_volhdr(struct fdisk_context *cxt) +{ + size_t n; + + for (n = 8; n < cxt->label->nparts_max; n++) { + if (!sgi_get_num_sectors(cxt, n)) { + /* Choose same default volume header size as IRIX fx uses. */ + if (4096 < sgi_get_lastblock(cxt)) + set_partition(cxt, n, 0, 4096, SGI_TYPE_VOLHDR); + break; + } + } +} + +static int sgi_delete_partition(struct fdisk_context *cxt, size_t partnum) +{ + int rc; + + assert(cxt); + assert(cxt->label); + + if (partnum > cxt->label->nparts_max) + return -EINVAL; + + rc = set_partition(cxt, partnum, 0, 0, 0); + + cxt->label->nparts_cur = count_used_partitions(cxt); + + return rc; +} + +static int sgi_add_partition(struct fdisk_context *cxt, + struct fdisk_partition *pa, + size_t *partno) +{ + struct fdisk_sgi_label *sgi; + char mesg[256]; + unsigned int first = 0, last = 0; + struct fdisk_ask *ask; + int sys = pa && pa->type ? pa->type->code : SGI_TYPE_XFS; + int rc; + size_t n; + + assert(cxt); + assert(cxt->label); + assert(fdisk_is_label(cxt, SGI)); + + rc = fdisk_partition_next_partno(pa, cxt, &n); + if (rc) + return rc; + if (n == 10) + sys = SGI_TYPE_ENTIRE_DISK; + else if (n == 8) + sys = 0; + + sgi = self_label(cxt); + + if (sgi_get_num_sectors(cxt, n)) { + fdisk_warnx(cxt, _("Partition %zu is already defined. " + "Delete it before re-adding it."), n + 1); + return -EINVAL; + } + if (!cxt->script && sgi_entire(cxt) == -1 && sys != SGI_TYPE_ENTIRE_DISK) { + fdisk_info(cxt, _("Attempting to generate entire disk entry automatically.")); + sgi_set_entire(cxt); + sgi_set_volhdr(cxt); + } + if (sgi_gaps(cxt) == 0 && sys != SGI_TYPE_ENTIRE_DISK) { + fdisk_warnx(cxt, _("The entire disk is already covered with partitions.")); + return -EINVAL; + } + if (sgi_gaps(cxt) < 0) { + fdisk_warnx(cxt, _("You got a partition overlap on the disk. Fix it first!")); + return -EINVAL; + } + + if (sys == SGI_TYPE_ENTIRE_DISK) { + first = 0; + last = sgi_get_lastblock(cxt); + } else { + first = sgi->freelist[0].first; + last = sgi->freelist[0].last; + } + + /* first sector */ + if (pa && pa->start_follow_default) + ; + else if (pa && fdisk_partition_has_start(pa)) { + first = pa->start; + last = is_in_freelist(cxt, first); + + if (sys != SGI_TYPE_ENTIRE_DISK && !last) + return -ERANGE; + } else { + snprintf(mesg, sizeof(mesg), _("First %s"), + fdisk_get_unit(cxt, FDISK_SINGULAR)); + ask = fdisk_new_ask(); + if (!ask) + return -ENOMEM; + + fdisk_ask_set_query(ask, mesg); + fdisk_ask_set_type(ask, FDISK_ASKTYPE_NUMBER); + + fdisk_ask_number_set_low(ask, fdisk_scround(cxt, first)); /* minimal */ + fdisk_ask_number_set_default(ask, fdisk_scround(cxt, first)); /* default */ + fdisk_ask_number_set_high(ask, fdisk_scround(cxt, last) - 1); /* maximal */ + + rc = fdisk_do_ask(cxt, ask); + first = fdisk_ask_number_get_result(ask); + fdisk_unref_ask(ask); + + if (rc) + return rc; + if (fdisk_use_cylinders(cxt)) + first *= fdisk_get_units_per_sector(cxt); + } + + if (first && sys == SGI_TYPE_ENTIRE_DISK) + fdisk_info(cxt, _("It is highly recommended that the " + "eleventh partition covers the entire " + "disk and is of type 'SGI volume'.")); + if (!last) + last = is_in_freelist(cxt, first); + + /* last sector */ + if (pa && pa->end_follow_default) + last -= 1ULL; + else if (pa && fdisk_partition_has_size(pa)) { + if (first + pa->size - 1ULL > last) + return -ERANGE; + last = first + pa->size - 1ULL; + } else { + snprintf(mesg, sizeof(mesg), + _("Last %s or +%s or +size{K,M,G,T,P}"), + fdisk_get_unit(cxt, FDISK_SINGULAR), + fdisk_get_unit(cxt, FDISK_PLURAL)); + + ask = fdisk_new_ask(); + if (!ask) + return -ENOMEM; + + fdisk_ask_set_query(ask, mesg); + fdisk_ask_set_type(ask, FDISK_ASKTYPE_OFFSET); + + fdisk_ask_number_set_low(ask, fdisk_scround(cxt, first)); /* minimal */ + fdisk_ask_number_set_default(ask, fdisk_scround(cxt, last) - 1);/* default */ + fdisk_ask_number_set_high(ask, fdisk_scround(cxt, last) - 1);/* maximal */ + fdisk_ask_number_set_base(ask, fdisk_scround(cxt, first)); + + if (fdisk_use_cylinders(cxt)) + fdisk_ask_number_set_unit(ask, + cxt->sector_size * + fdisk_get_units_per_sector(cxt)); + else + fdisk_ask_number_set_unit(ask,cxt->sector_size); + + rc = fdisk_do_ask(cxt, ask); + last = fdisk_ask_number_get_result(ask) + 1; + + fdisk_unref_ask(ask); + if (rc) + return rc; + if (fdisk_use_cylinders(cxt)) + last *= fdisk_get_units_per_sector(cxt); + } + + if (sys == SGI_TYPE_ENTIRE_DISK + && (first != 0 || last != sgi_get_lastblock(cxt))) + fdisk_info(cxt, _("It is highly recommended that the " + "eleventh partition covers the entire " + "disk and is of type 'SGI volume'.")); + + set_partition(cxt, n, first, last - first, sys); + cxt->label->nparts_cur = count_used_partitions(cxt); + if (partno) + *partno = n; + return 0; +} + +static int sgi_create_disklabel(struct fdisk_context *cxt) +{ + struct fdisk_sgi_label *sgi; + struct sgi_disklabel *sgilabel; + int rc; + + assert(cxt); + assert(cxt->label); + assert(fdisk_is_label(cxt, SGI)); + +#ifdef HDIO_GETGEO + if (cxt->geom.heads && cxt->geom.sectors) { + fdisk_sector_t llsectors; + + if (blkdev_get_sectors(cxt->dev_fd, (unsigned long long *) &llsectors) == 0) { + /* the get device size ioctl was successful */ + fdisk_sector_t llcyls; + int sec_fac = cxt->sector_size / 512; + + llcyls = llsectors / (cxt->geom.heads * cxt->geom.sectors * sec_fac); + cxt->geom.cylinders = llcyls; + if (cxt->geom.cylinders != llcyls) /* truncated? */ + cxt->geom.cylinders = ~0; + } else { + /* otherwise print error and use truncated version */ + fdisk_warnx(cxt, + _("BLKGETSIZE ioctl failed on %s. " + "Using geometry cylinder value of %llu. " + "This value may be truncated for devices " + "> 33.8 GB."), cxt->dev_path, cxt->geom.cylinders); + } + } +#endif + rc = fdisk_init_firstsector_buffer(cxt); + if (rc) + return rc; + + sgi = (struct fdisk_sgi_label *) cxt->label; + sgi->header = (struct sgi_disklabel *) cxt->firstsector; + + sgilabel = sgi->header; + + sgilabel->magic = cpu_to_be32(SGI_LABEL_MAGIC); + sgilabel->root_part_num = cpu_to_be16(0); + sgilabel->swap_part_num = cpu_to_be16(1); + + /* sizeof(sgilabel->boot_file) = 16 > 6 */ + memset(sgilabel->boot_file, 0, 16); + strcpy((char *) sgilabel->boot_file, "/unix"); + + sgilabel->devparam.skew = (0); + sgilabel->devparam.gap1 = (0); + sgilabel->devparam.gap2 = (0); + sgilabel->devparam.sparecyl = (0); + sgilabel->devparam.pcylcount = cpu_to_be16(cxt->geom.cylinders); + sgilabel->devparam.head_vol0 = cpu_to_be16(0); + sgilabel->devparam.ntrks = cpu_to_be16(cxt->geom.heads); + /* tracks/cylinder (heads) */ + sgilabel->devparam.cmd_tag_queue_depth = (0); + sgilabel->devparam.unused0 = (0); + sgilabel->devparam.unused1 = cpu_to_be16(0); + sgilabel->devparam.nsect = cpu_to_be16(cxt->geom.sectors); + /* sectors/track */ + sgilabel->devparam.bytes = cpu_to_be16(cxt->sector_size); + sgilabel->devparam.ilfact = cpu_to_be16(1); + sgilabel->devparam.flags = cpu_to_be32( + SGI_DEVPARAM_TRACK_FWD + | SGI_DEVPARAM_IGNORE_ERRORS + | SGI_DEVPARAM_RESEEK); + sgilabel->devparam.datarate = cpu_to_be32(0); + sgilabel->devparam.retries_on_error = cpu_to_be32(1); + sgilabel->devparam.ms_per_word = cpu_to_be32(0); + sgilabel->devparam.xylogics_gap1 = cpu_to_be16(0); + sgilabel->devparam.xylogics_syncdelay = cpu_to_be16(0); + sgilabel->devparam.xylogics_readdelay = cpu_to_be16(0); + sgilabel->devparam.xylogics_gap2 = cpu_to_be16(0); + sgilabel->devparam.xylogics_readgate = cpu_to_be16(0); + sgilabel->devparam.xylogics_writecont = cpu_to_be16(0); + + memset(&(sgilabel->volume), 0, + sizeof(struct sgi_volume) * SGI_MAXVOLUMES); + memset(&(sgilabel->partitions), 0, + sizeof(struct sgi_partition) * SGI_MAXPARTITIONS); + cxt->label->nparts_max = SGI_MAXPARTITIONS; + + /* don't create default layout when a script defined */ + if (!cxt->script) { + sgi_set_entire(cxt); + sgi_set_volhdr(cxt); + } + cxt->label->nparts_cur = count_used_partitions(cxt); + + fdisk_info(cxt, _("Created a new SGI disklabel.")); + return 0; +} + +static int sgi_set_partition(struct fdisk_context *cxt, + size_t i, + struct fdisk_partition *pa) +{ + struct sgi_disklabel *sgilabel; + + if (i >= cxt->label->nparts_max) + return -EINVAL; + + sgilabel = self_disklabel(cxt); + + if (pa->type) { + struct fdisk_parttype *t = pa->type; + + if (t->code > UINT32_MAX) + return -EINVAL; + + if (sgi_get_num_sectors(cxt, i) == 0) /* caught already before, ... */ { + fdisk_warnx(cxt, _("Sorry, only for non-empty partitions you can change the tag.")); + return -EINVAL; + } + + if ((i == 10 && t->code != SGI_TYPE_ENTIRE_DISK) + || (i == 8 && t->code != 0)) + fdisk_info(cxt, _("Consider leaving partition 9 as volume header (0), " + "and partition 11 as entire volume (6), " + "as IRIX expects it.")); + + if (cxt->script == NULL + && ((t->code != SGI_TYPE_ENTIRE_DISK) && (t->code != SGI_TYPE_VOLHDR)) + && (sgi_get_start_sector(cxt, i) < 1)) { + int yes = 0; + fdisk_ask_yesno(cxt, + _("It is highly recommended that the partition at offset 0 " + "is of type \"SGI volhdr\", the IRIX system will rely on it to " + "retrieve from its directory standalone tools like sash and fx. " + "Only the \"SGI volume\" entire disk section may violate this. " + "Are you sure about tagging this partition differently?"), &yes); + if (!yes) + return 1; + } + + sgilabel->partitions[i].type = cpu_to_be32(t->code); + } + + if (fdisk_partition_has_start(pa)) + sgilabel->partitions[i].first_block = cpu_to_be32(pa->start); + if (fdisk_partition_has_size(pa)) + sgilabel->partitions[i].num_blocks = cpu_to_be32(pa->size); + + fdisk_label_set_changed(cxt->label, 1); + return 0; +} + + +static int sgi_partition_is_used( + struct fdisk_context *cxt, + size_t i) +{ + assert(cxt); + assert(fdisk_is_label(cxt, SGI)); + + if (i >= cxt->label->nparts_max) + return 0; + return sgi_get_num_sectors(cxt, i) ? 1 : 0; +} + +static int sgi_toggle_partition_flag(struct fdisk_context *cxt, size_t i, unsigned long flag) +{ + struct sgi_disklabel *sgilabel; + assert(cxt); + assert(cxt->label); + assert(fdisk_is_label(cxt, SGI)); + + if (i >= cxt->label->nparts_max) + return -EINVAL; + + sgilabel = self_disklabel(cxt); + + switch (flag) { + case SGI_FLAG_BOOT: + sgilabel->root_part_num = + be16_to_cpu(sgilabel->root_part_num) == i ? + 0 : cpu_to_be16(i); + fdisk_label_set_changed(cxt->label, 1); + break; + case SGI_FLAG_SWAP: + sgilabel->swap_part_num = + be16_to_cpu(sgilabel->swap_part_num) == i ? + 0 : cpu_to_be16(i); + fdisk_label_set_changed(cxt->label, 1); + break; + default: + return 1; + } + + return 0; +} + +static const struct fdisk_field sgi_fields[] = +{ + { FDISK_FIELD_DEVICE, N_("Device"), 10, 0 }, + { FDISK_FIELD_START, N_("Start"), 5, FDISK_FIELDFL_NUMBER }, + { FDISK_FIELD_END, N_("End"), 5, FDISK_FIELDFL_NUMBER }, + { FDISK_FIELD_SECTORS, N_("Sectors"), 5, FDISK_FIELDFL_NUMBER }, + { FDISK_FIELD_CYLINDERS,N_("Cylinders"), 5, FDISK_FIELDFL_NUMBER }, + { FDISK_FIELD_SIZE, N_("Size"), 5, FDISK_FIELDFL_NUMBER | FDISK_FIELDFL_EYECANDY }, + { FDISK_FIELD_TYPEID, N_("Id"), 2, FDISK_FIELDFL_NUMBER }, + { FDISK_FIELD_TYPE, N_("Type"), 0.1, FDISK_FIELDFL_EYECANDY }, + { FDISK_FIELD_ATTR, N_("Attrs"), 0, FDISK_FIELDFL_NUMBER } +}; + +static const struct fdisk_label_operations sgi_operations = +{ + .probe = sgi_probe_label, + .write = sgi_write_disklabel, + .verify = sgi_verify_disklabel, + .create = sgi_create_disklabel, + .list = sgi_list_table, + + .get_part = sgi_get_partition, + .set_part = sgi_set_partition, + .add_part = sgi_add_partition, + .del_part = sgi_delete_partition, + + .part_is_used = sgi_partition_is_used, + .part_toggle_flag = sgi_toggle_partition_flag +}; + +/* Allocates an SGI label driver. */ +struct fdisk_label *fdisk_new_sgi_label(struct fdisk_context *cxt) +{ + struct fdisk_label *lb; + struct fdisk_sgi_label *sgi; + + assert(cxt); + + sgi = calloc(1, sizeof(*sgi)); + if (!sgi) + return NULL; + + /* initialize generic part of the driver */ + lb = (struct fdisk_label *) sgi; + lb->name = "sgi"; + lb->id = FDISK_DISKLABEL_SGI; + lb->op = &sgi_operations; + lb->parttypes = sgi_parttypes; + lb->nparttypes = ARRAY_SIZE(sgi_parttypes) - 1; + lb->fields = sgi_fields; + lb->nfields = ARRAY_SIZE(sgi_fields); + + lb->flags |= FDISK_LABEL_FL_REQUIRE_GEOMETRY; + + return lb; +} diff --git a/libblkid/libfdisk/src/sun.c b/libblkid/libfdisk/src/sun.c new file mode 100644 index 000000000..babff6263 --- /dev/null +++ b/libblkid/libfdisk/src/sun.c @@ -0,0 +1,1130 @@ +/* + * Copyright (C) 2013 Karel Zak <kzak@redhat.com> + * + * Based on original code from fdisk: + * Jakub Jelinek (jj@sunsite.mff.cuni.cz), July 1996 + * Merged with fdisk for other architectures, aeb, June 1998. + * Arnaldo Carvalho de Melo <acme@conectiva.com.br> Mar 1999, Internationalization + */ +#include <stdio.h> /* stderr */ +#include <stdlib.h> /* qsort */ +#include <string.h> /* strstr */ +#include <unistd.h> /* write */ +#include <sys/ioctl.h> /* ioctl */ + +#include "nls.h" +#include "blkdev.h" +#include "bitops.h" + +#include "fdiskP.h" +#include "pt-sun.h" +#include "all-io.h" + + +/** + * SECTION: sun + * @title: SUN + * @short_description: disk label specific functions + * + */ + +/* + * in-memory fdisk SUN stuff + */ +struct fdisk_sun_label { + struct fdisk_label head; /* generic part */ + struct sun_disklabel *header; /* on-disk data (pointer to cxt->firstsector) */ +}; + +static struct fdisk_parttype sun_parttypes[] = { + {SUN_TAG_UNASSIGNED, N_("Unassigned")}, + {SUN_TAG_BOOT, N_("Boot")}, + {SUN_TAG_ROOT, N_("SunOS root")}, + {SUN_TAG_SWAP, N_("SunOS swap")}, + {SUN_TAG_USR, N_("SunOS usr")}, + {SUN_TAG_WHOLEDISK, N_("Whole disk")}, + {SUN_TAG_STAND, N_("SunOS stand")}, + {SUN_TAG_VAR, N_("SunOS var")}, + {SUN_TAG_HOME, N_("SunOS home")}, + {SUN_TAG_ALTSCTR, N_("SunOS alt sectors")}, + {SUN_TAG_CACHE, N_("SunOS cachefs")}, + {SUN_TAG_RESERVED, N_("SunOS reserved")}, + {SUN_TAG_LINUX_SWAP, N_("Linux swap")}, + {SUN_TAG_LINUX_NATIVE, N_("Linux native")}, + {SUN_TAG_LINUX_LVM, N_("Linux LVM")}, + {SUN_TAG_LINUX_RAID, N_("Linux raid autodetect")}, + { 0, NULL } +}; + +/* return poiter buffer with on-disk data */ +static inline struct sun_disklabel *self_disklabel(struct fdisk_context *cxt) +{ + assert(cxt); + assert(cxt->label); + assert(fdisk_is_label(cxt, SUN)); + + return ((struct fdisk_sun_label *) cxt->label)->header; +} + +/* return in-memory sun fdisk data */ +static inline struct fdisk_sun_label *self_label(struct fdisk_context *cxt) +{ + assert(cxt); + assert(cxt->label); + assert(fdisk_is_label(cxt, SUN)); + + return (struct fdisk_sun_label *) cxt->label; +} + +static void set_partition(struct fdisk_context *cxt, size_t i, + uint32_t start,uint32_t stop, uint16_t sysid) +{ + struct sun_disklabel *sunlabel = self_disklabel(cxt); + struct fdisk_parttype *t = + fdisk_label_get_parttype_from_code(cxt->label, sysid); + + sunlabel->vtoc.infos[i].id = cpu_to_be16(sysid); + sunlabel->vtoc.infos[i].flags = cpu_to_be16(0); + sunlabel->partitions[i].start_cylinder = + cpu_to_be32(start / (cxt->geom.heads * cxt->geom.sectors)); + sunlabel->partitions[i].num_sectors = cpu_to_be32(stop - start); + fdisk_label_set_changed(cxt->label, 1); + + fdisk_info_new_partition(cxt, i + 1, start, stop, t); +} + +static size_t count_used_partitions(struct fdisk_context *cxt) +{ + struct sun_disklabel *sunlabel = self_disklabel(cxt); + size_t ct = 0, i; + + assert(sunlabel); + + for (i = 0; i < cxt->label->nparts_max; i++) { + if (sunlabel->partitions[i].num_sectors) + ct++; + } + return ct; +} + +static int sun_probe_label(struct fdisk_context *cxt) +{ + struct fdisk_sun_label *sun; + struct sun_disklabel *sunlabel; + unsigned short *ush; + int csum; + int need_fixing = 0; + + assert(cxt); + assert(cxt->label); + assert(fdisk_is_label(cxt, SUN)); + + /* map first sector to header */ + sun = (struct fdisk_sun_label *) cxt->label; + sun->header = (struct sun_disklabel *) cxt->firstsector; + sunlabel = sun->header; + + if (be16_to_cpu(sunlabel->magic) != SUN_LABEL_MAGIC) { + sun->header = NULL; + return 0; /* failed */ + } + + ush = ((unsigned short *) (sunlabel + 1)) - 1; + for (csum = 0; ush >= (unsigned short *)sunlabel;) + csum ^= *ush--; + + if (csum) { + fdisk_warnx(cxt, _("Detected sun disklabel with wrong checksum. " + "Probably you'll have to set all the values, " + "e.g. heads, sectors, cylinders and partitions " + "or force a fresh label (s command in main menu)")); + return 1; + } + + cxt->label->nparts_max = SUN_MAXPARTITIONS; + cxt->geom.heads = be16_to_cpu(sunlabel->nhead); + cxt->geom.cylinders = be16_to_cpu(sunlabel->ncyl); + cxt->geom.sectors = be16_to_cpu(sunlabel->nsect); + + if (be32_to_cpu(sunlabel->vtoc.version) != SUN_VTOC_VERSION) { + fdisk_warnx(cxt, _("Detected sun disklabel with wrong version [%d]."), + be32_to_cpu(sunlabel->vtoc.version)); + need_fixing = 1; + } + if (be32_to_cpu(sunlabel->vtoc.sanity) != SUN_VTOC_SANITY) { + fdisk_warnx(cxt, _("Detected sun disklabel with wrong vtoc.sanity [0x%08x]."), + be32_to_cpu(sunlabel->vtoc.sanity)); + need_fixing = 1; + } + if (be16_to_cpu(sunlabel->vtoc.nparts) != SUN_MAXPARTITIONS) { + fdisk_warnx(cxt, _("Detected sun disklabel with wrong vtoc.nparts [%u]."), + be16_to_cpu(sunlabel->vtoc.nparts)); + need_fixing = 1; + } + if (need_fixing) { + fdisk_warnx(cxt, _("Warning: Wrong values need to be fixed up and " + "will be corrected by w(rite)")); + + sunlabel->vtoc.version = cpu_to_be32(SUN_VTOC_VERSION); + sunlabel->vtoc.sanity = cpu_to_be32(SUN_VTOC_SANITY); + sunlabel->vtoc.nparts = cpu_to_be16(SUN_MAXPARTITIONS); + + ush = (unsigned short *)sunlabel; + csum = 0; + while(ush < (unsigned short *)(&sunlabel->csum)) + csum ^= *ush++; + sunlabel->csum = csum; + + fdisk_label_set_changed(cxt->label, 1); + } + + cxt->label->nparts_cur = count_used_partitions(cxt); + + return 1; +} + +static void ask_geom(struct fdisk_context *cxt) +{ + uintmax_t res; + + assert(cxt); + + if (fdisk_ask_number(cxt, 1, 1, 1024, _("Heads"), &res) == 0) + cxt->geom.heads = res; + if (fdisk_ask_number(cxt, 1, 1, 1024, _("Sectors/track"), &res) == 0) + cxt->geom.sectors = res; + if (fdisk_ask_number(cxt, 1, 1, USHRT_MAX, _("Cylinders"), &res) == 0) + cxt->geom.cylinders = res; +} + +static int sun_create_disklabel(struct fdisk_context *cxt) +{ + unsigned int ndiv; + struct fdisk_sun_label *sun; /* libfdisk sun handler */ + struct sun_disklabel *sunlabel; /* on disk data */ + int rc = 0; + + assert(cxt); + assert(cxt->label); + assert(fdisk_is_label(cxt, SUN)); + + /* map first sector to header */ + rc = fdisk_init_firstsector_buffer(cxt); + if (rc) + return rc; + + sun = (struct fdisk_sun_label *) cxt->label; + sun->header = (struct sun_disklabel *) cxt->firstsector; + + sunlabel = sun->header; + + cxt->label->nparts_max = SUN_MAXPARTITIONS; + + sunlabel->magic = cpu_to_be16(SUN_LABEL_MAGIC); + sunlabel->vtoc.version = cpu_to_be32(SUN_VTOC_VERSION); + sunlabel->vtoc.sanity = cpu_to_be32(SUN_VTOC_SANITY); + sunlabel->vtoc.nparts = cpu_to_be16(SUN_MAXPARTITIONS); + +#ifdef HDIO_GETGEO + if (cxt->geom.heads && cxt->geom.sectors) { + fdisk_sector_t llsectors; + + if (blkdev_get_sectors(cxt->dev_fd, (unsigned long long *) &llsectors) == 0) { + int sec_fac = cxt->sector_size / 512; + fdisk_sector_t llcyls; + + llcyls = llsectors / (cxt->geom.heads * cxt->geom.sectors * sec_fac); + cxt->geom.cylinders = llcyls; + if (cxt->geom.cylinders != llcyls) + cxt->geom.cylinders = ~0; + } else { + fdisk_warnx(cxt, + _("BLKGETSIZE ioctl failed on %s. " + "Using geometry cylinder value of %llu. " + "This value may be truncated for devices " + "> 33.8 GB."), + cxt->dev_path, cxt->geom.cylinders); + } + } else +#endif + ask_geom(cxt); + + sunlabel->acyl = cpu_to_be16(0); + sunlabel->pcyl = cpu_to_be16(cxt->geom.cylinders); + sunlabel->rpm = cpu_to_be16(5400); + sunlabel->intrlv = cpu_to_be16(1); + sunlabel->apc = cpu_to_be16(0); + + sunlabel->nhead = cpu_to_be16(cxt->geom.heads); + sunlabel->nsect = cpu_to_be16(cxt->geom.sectors); + sunlabel->ncyl = cpu_to_be16(cxt->geom.cylinders); + + snprintf((char *) sunlabel->label_id, sizeof(sunlabel->label_id), + "Linux cyl %ju alt %u hd %u sec %ju", + (uintmax_t) cxt->geom.cylinders, + be16_to_cpu(sunlabel->acyl), + cxt->geom.heads, + (uintmax_t) cxt->geom.sectors); + + if (cxt->geom.cylinders * cxt->geom.heads * cxt->geom.sectors >= 150 * 2048) { + ndiv = cxt->geom.cylinders - (50 * 2048 / (cxt->geom.heads * cxt->geom.sectors)); /* 50M swap */ + } else + ndiv = cxt->geom.cylinders * 2 / 3; + + /* create the default layout only if no-script defined */ + if (!cxt->script) { + set_partition(cxt, 0, 0, ndiv * cxt->geom.heads * cxt->geom.sectors, + SUN_TAG_LINUX_NATIVE); + set_partition(cxt, 1, ndiv * cxt->geom.heads * cxt->geom.sectors, + cxt->geom.cylinders * cxt->geom.heads * cxt->geom.sectors, + SUN_TAG_LINUX_SWAP); + sunlabel->vtoc.infos[1].flags |= cpu_to_be16(SUN_FLAG_UNMNT); + + set_partition(cxt, 2, 0, + cxt->geom.cylinders * cxt->geom.heads * cxt->geom.sectors, + SUN_TAG_WHOLEDISK); + } + + { + unsigned short *ush = (unsigned short *)sunlabel; + unsigned short csum = 0; + while(ush < (unsigned short *)(&sunlabel->csum)) + csum ^= *ush++; + sunlabel->csum = csum; + } + + fdisk_label_set_changed(cxt->label, 1); + cxt->label->nparts_cur = count_used_partitions(cxt); + + fdisk_info(cxt, _("Created a new Sun disklabel.")); + return 0; +} + +static int sun_toggle_partition_flag(struct fdisk_context *cxt, size_t i, unsigned long flag) +{ + struct sun_disklabel *sunlabel; + struct sun_info *p; + + assert(cxt); + assert(cxt->label); + assert(fdisk_is_label(cxt, SUN)); + + if (i >= cxt->label->nparts_max) + return -EINVAL; + + sunlabel = self_disklabel(cxt); + p = &sunlabel->vtoc.infos[i]; + + switch (flag) { + case SUN_FLAG_UNMNT: + p->flags ^= cpu_to_be16(SUN_FLAG_UNMNT); + fdisk_label_set_changed(cxt->label, 1); + break; + case SUN_FLAG_RONLY: + p->flags ^= cpu_to_be16(SUN_FLAG_RONLY); + fdisk_label_set_changed(cxt->label, 1); + break; + default: + return 1; + } + + return 0; +} + +static void fetch_sun(struct fdisk_context *cxt, + uint32_t *starts, + uint32_t *lens, + uint32_t *start, + uint32_t *stop) +{ + struct sun_disklabel *sunlabel; + int continuous = 1; + size_t i; + + assert(cxt); + assert(cxt); + assert(cxt->label); + assert(fdisk_is_label(cxt, SUN)); + + sunlabel = self_disklabel(cxt); + + *start = 0; + *stop = cxt->geom.cylinders * cxt->geom.heads * cxt->geom.sectors; + + for (i = 0; i < cxt->label->nparts_max; i++) { + struct sun_partition *part = &sunlabel->partitions[i]; + struct sun_info *info = &sunlabel->vtoc.infos[i]; + + if (part->num_sectors && + be16_to_cpu(info->id) != SUN_TAG_UNASSIGNED && + be16_to_cpu(info->id) != SUN_TAG_WHOLEDISK) { + starts[i] = be32_to_cpu(part->start_cylinder) * + cxt->geom.heads * cxt->geom.sectors; + lens[i] = be32_to_cpu(part->num_sectors); + if (continuous) { + if (starts[i] == *start) + *start += lens[i]; + else if (starts[i] + lens[i] >= *stop) + *stop = starts[i]; + else + continuous = 0; + /* There will be probably more gaps + than one, so lets check afterwards */ + } + } else { + starts[i] = 0; + lens[i] = 0; + } + } +} + +#ifdef HAVE_QSORT_R +static int verify_sun_cmp(int *a, int *b, void *data) +{ + unsigned int *verify_sun_starts = (unsigned int *) data; + + if (*a == -1) + return 1; + if (*b == -1) + return -1; + if (verify_sun_starts[*a] > verify_sun_starts[*b]) + return 1; + return -1; +} +#endif + +static int sun_verify_disklabel(struct fdisk_context *cxt) +{ + uint32_t starts[SUN_MAXPARTITIONS], lens[SUN_MAXPARTITIONS], start, stop; + uint32_t i,j,k,starto,endo; +#ifdef HAVE_QSORT_R + int array[SUN_MAXPARTITIONS]; + unsigned int *verify_sun_starts; +#endif + assert(cxt); + assert(cxt->label); + assert(fdisk_is_label(cxt, SUN)); + + fetch_sun(cxt, starts, lens, &start, &stop); + + for (k = 0; k < 7; k++) { + for (i = 0; i < SUN_MAXPARTITIONS; i++) { + if (k && (lens[i] % (cxt->geom.heads * cxt->geom.sectors))) + fdisk_warnx(cxt, _("Partition %u doesn't end on cylinder boundary."), i+1); + if (lens[i]) { + for (j = 0; j < i; j++) + if (lens[j]) { + if (starts[j] == starts[i]+lens[i]) { + starts[j] = starts[i]; lens[j] += lens[i]; + lens[i] = 0; + } else if (starts[i] == starts[j]+lens[j]){ + lens[j] += lens[i]; + lens[i] = 0; + } else if (!k) { + if (starts[i] < starts[j]+lens[j] && + starts[j] < starts[i]+lens[i]) { + starto = starts[i]; + if (starts[j] > starto) + starto = starts[j]; + endo = starts[i]+lens[i]; + if (starts[j]+lens[j] < endo) + endo = starts[j]+lens[j]; + fdisk_warnx(cxt, _("Partition %u overlaps with others in " + "sectors %u-%u."), i+1, starto, endo); + } + } + } + } + } + } + +#ifdef HAVE_QSORT_R + for (i = 0; i < SUN_MAXPARTITIONS; i++) { + if (lens[i]) + array[i] = i; + else + array[i] = -1; + } + verify_sun_starts = starts; + + qsort_r(array,ARRAY_SIZE(array),sizeof(array[0]), + (int (*)(const void *,const void *,void *)) verify_sun_cmp, + verify_sun_starts); + + if (array[0] == -1) { + fdisk_info(cxt, _("No partitions defined.")); + return 0; + } + stop = cxt->geom.cylinders * cxt->geom.heads * cxt->geom.sectors; + if (starts[array[0]]) + fdisk_warnx(cxt, _("Unused gap - sectors 0-%u."), starts[array[0]]); + for (i = 0; i < 7 && array[i+1] != -1; i++) { + fdisk_warnx(cxt, _("Unused gap - sectors %u-%u."), + (starts[array[i]] + lens[array[i]]), + starts[array[i+1]]); + } + start = (starts[array[i]] + lens[array[i]]); + if (start < stop) + fdisk_warnx(cxt, _("Unused gap - sectors %u-%u."), start, stop); +#endif + return 0; +} + + +static int is_free_sector(struct fdisk_context *cxt, + fdisk_sector_t s, uint32_t starts[], uint32_t lens[]) +{ + size_t i; + + for (i = 0; i < cxt->label->nparts_max; i++) { + if (lens[i] && starts[i] <= s + && starts[i] + lens[i] > s) + return 0; + } + return 1; +} + +static int sun_add_partition( + struct fdisk_context *cxt, + struct fdisk_partition *pa, + size_t *partno) +{ + struct sun_disklabel *sunlabel = self_disklabel(cxt); + uint32_t starts[SUN_MAXPARTITIONS], lens[SUN_MAXPARTITIONS]; + struct sun_partition *part; + struct sun_info *info; + uint32_t start, stop, stop2; + int whole_disk = 0; + int sys = pa && pa->type ? pa->type->code : SUN_TAG_LINUX_NATIVE; + int rc; + size_t n; + + char mesg[256]; + size_t i; + unsigned int first, last; + + rc = fdisk_partition_next_partno(pa, cxt, &n); + if (rc) + return rc; + + part = &sunlabel->partitions[n]; + info = &sunlabel->vtoc.infos[n]; + + if (part->num_sectors && be16_to_cpu(info->id) != SUN_TAG_UNASSIGNED) { + fdisk_info(cxt, _("Partition %zu is already defined. Delete " + "it before re-adding it."), n + 1); + return -EINVAL; + } + + fetch_sun(cxt, starts, lens, &start, &stop); + + if (stop <= start) { + if (n == 2) + whole_disk = 1; + else { + fdisk_info(cxt, _("Other partitions already cover the " + "whole disk. Delete some/shrink them before retry.")); + return -EINVAL; + } + } + + if (pa && pa->start_follow_default) + first = start; + else if (pa && fdisk_partition_has_start(pa)) { + first = pa->start; + + if (!whole_disk && !is_free_sector(cxt, first, starts, lens)) + return -ERANGE; + } else { + struct fdisk_ask *ask; + + snprintf(mesg, sizeof(mesg), _("First %s"), + fdisk_get_unit(cxt, FDISK_SINGULAR)); + for (;;) { + ask = fdisk_new_ask(); + if (!ask) + return -ENOMEM; + + fdisk_ask_set_query(ask, mesg); + fdisk_ask_set_type(ask, FDISK_ASKTYPE_NUMBER); + + if (whole_disk) { + fdisk_ask_number_set_low(ask, 0); /* minimal */ + fdisk_ask_number_set_default(ask, 0); /* default */ + fdisk_ask_number_set_high(ask, 0); /* maximal */ + } else { + fdisk_ask_number_set_low(ask, fdisk_scround(cxt, start)); /* minimal */ + fdisk_ask_number_set_default(ask, fdisk_scround(cxt, start)); /* default */ + fdisk_ask_number_set_high(ask, fdisk_scround(cxt, stop)); /* maximal */ + } + rc = fdisk_do_ask(cxt, ask); + first = fdisk_ask_number_get_result(ask); + fdisk_unref_ask(ask); + if (rc) + return rc; + + if (fdisk_use_cylinders(cxt)) + first *= fdisk_get_units_per_sector(cxt); + + /* ewt asks to add: "don't start a partition at cyl 0" + However, edmundo@rano.demon.co.uk writes: + "In addition to having a Sun partition table, to be able to + boot from the disc, the first partition, /dev/sdX1, must + start at cylinder 0. This means that /dev/sdX1 contains + the partition table and the boot block, as these are the + first two sectors of the disc. Therefore you must be + careful what you use /dev/sdX1 for. In particular, you must + not use a partition starting at cylinder 0 for Linux swap, + as that would overwrite the partition table and the boot + block. You may, however, use such a partition for a UFS + or EXT2 file system, as these file systems leave the first + 1024 bytes undisturbed. */ + /* On the other hand, one should not use partitions + starting at block 0 in an md, or the label will + be trashed. */ + if (!is_free_sector(cxt, first, starts, lens) && !whole_disk) { + if (n == 2 && !first) { + whole_disk = 1; + break; + } + fdisk_warnx(cxt, _("Sector %d is already allocated"), first); + } else + break; + } + } + + if (n == 2 && first != 0) + fdisk_warnx(cxt, _("It is highly recommended that the " + "third partition covers the whole disk " + "and is of type `Whole disk'")); + + if (!fdisk_use_cylinders(cxt)) { + /* Starting sector has to be properly aligned */ + int cs = cxt->geom.heads * cxt->geom.sectors; + int x = first % cs; + + if (x) { + fdisk_info(cxt, _("Aligning the first sector from %u to %u " + "to be on cylinder boundary."), + first, first + cs - x); + first += cs - x; + } + } + + stop = cxt->geom.cylinders * cxt->geom.heads * cxt->geom.sectors; /* ancient */ + stop2 = stop; + for (i = 0; i < cxt->label->nparts_max; i++) { + if (starts[i] > first && starts[i] < stop) + stop = starts[i]; + } + + /* last */ + if (pa && pa->end_follow_default) + last = whole_disk || (n == 2 && !first) ? stop2 : stop; + else if (pa && fdisk_partition_has_size(pa)) { + last = first + pa->size - 1ULL; + + if (!whole_disk && last > stop) + return -ERANGE; + } else { + struct fdisk_ask *ask = fdisk_new_ask(); + + if (!ask) + return -ENOMEM; + + snprintf(mesg, sizeof(mesg), + _("Last %s or +%s or +size{K,M,G,T,P}"), + fdisk_get_unit(cxt, FDISK_SINGULAR), + fdisk_get_unit(cxt, FDISK_PLURAL)); + fdisk_ask_set_query(ask, mesg); + fdisk_ask_set_type(ask, FDISK_ASKTYPE_OFFSET); + + if (whole_disk) { + fdisk_ask_number_set_low(ask, fdisk_scround(cxt, stop2)); /* minimal */ + fdisk_ask_number_set_default(ask, fdisk_scround(cxt, stop2)); /* default */ + fdisk_ask_number_set_high(ask, fdisk_scround(cxt, stop2)); /* maximal */ + fdisk_ask_number_set_base(ask, 0); + } else if (n == 2 && !first) { + fdisk_ask_number_set_low(ask, fdisk_scround(cxt, first)); /* minimal */ + fdisk_ask_number_set_default(ask, fdisk_scround(cxt, stop2)); /* default */ + fdisk_ask_number_set_high(ask, fdisk_scround(cxt, stop2)); /* maximal */ + fdisk_ask_number_set_base(ask, fdisk_scround(cxt, first)); + } else { + fdisk_ask_number_set_low(ask, fdisk_scround(cxt, first)); /* minimal */ + fdisk_ask_number_set_default(ask, fdisk_scround(cxt, stop)); /* default */ + fdisk_ask_number_set_high(ask, fdisk_scround(cxt, stop)); /* maximal */ + fdisk_ask_number_set_base(ask, fdisk_scround(cxt, first)); + } + + if (fdisk_use_cylinders(cxt)) + fdisk_ask_number_set_unit(ask, + cxt->sector_size * + fdisk_get_units_per_sector(cxt)); + else + fdisk_ask_number_set_unit(ask, cxt->sector_size); + + rc = fdisk_do_ask(cxt, ask); + last = fdisk_ask_number_get_result(ask); + + fdisk_unref_ask(ask); + if (rc) + return rc; + if (fdisk_use_cylinders(cxt)) + last *= fdisk_get_units_per_sector(cxt); + } + + if (n == 2 && !first) { + if (last >= stop2) { + whole_disk = 1; + last = stop2; + } else if (last > stop) { + fdisk_warnx(cxt, + _("You haven't covered the whole disk with the 3rd partition, but your value\n" + "%lu %s covers some other partition. Your entry has been changed\n" + "to %lu %s"), + (unsigned long) fdisk_scround(cxt, last), fdisk_get_unit(cxt, FDISK_SINGULAR), + (unsigned long) fdisk_scround(cxt, stop), fdisk_get_unit(cxt, FDISK_SINGULAR)); + last = stop; + } + } else if (!whole_disk && last > stop) + last = stop; + + if (whole_disk) + sys = SUN_TAG_WHOLEDISK; + + set_partition(cxt, n, first, last, sys); + cxt->label->nparts_cur = count_used_partitions(cxt); + if (partno) + *partno = n; + return 0; +} + +static int sun_delete_partition(struct fdisk_context *cxt, + size_t partnum) +{ + struct sun_disklabel *sunlabel; + struct sun_partition *part; + struct sun_info *info; + unsigned int nsec; + + assert(cxt); + assert(cxt->label); + assert(fdisk_is_label(cxt, SUN)); + + sunlabel = self_disklabel(cxt); + part = &sunlabel->partitions[partnum]; + info = &sunlabel->vtoc.infos[partnum]; + + if (partnum == 2 && + be16_to_cpu(info->id) == SUN_TAG_WHOLEDISK && + !part->start_cylinder && + (nsec = be32_to_cpu(part->num_sectors)) + == cxt->geom.heads * cxt->geom.sectors * cxt->geom.cylinders) + fdisk_info(cxt, _("If you want to maintain SunOS/Solaris compatibility, " + "consider leaving this " + "partition as Whole disk (5), starting at 0, with %u " + "sectors"), nsec); + info->id = cpu_to_be16(SUN_TAG_UNASSIGNED); + part->num_sectors = 0; + cxt->label->nparts_cur = count_used_partitions(cxt); + fdisk_label_set_changed(cxt->label, 1); + return 0; +} + + +static int sun_list_disklabel(struct fdisk_context *cxt) +{ + struct sun_disklabel *sunlabel; + + assert(cxt); + assert(cxt->label); + assert(fdisk_is_label(cxt, SUN)); + + sunlabel = self_disklabel(cxt); + + if (fdisk_is_details(cxt)) { + fdisk_info(cxt, + _("Label geometry: %d rpm, %d alternate and %d physical cylinders,\n" + " %d extra sects/cyl, interleave %d:1"), + be16_to_cpu(sunlabel->rpm), + be16_to_cpu(sunlabel->acyl), + be16_to_cpu(sunlabel->pcyl), + be16_to_cpu(sunlabel->apc), + be16_to_cpu(sunlabel->intrlv)); + fdisk_info(cxt, _("Label ID: %s"), sunlabel->label_id); + fdisk_info(cxt, _("Volume ID: %s"), + *sunlabel->vtoc.volume_id ? sunlabel->vtoc.volume_id : _("<none>")); + } + + return 0; +} + +static struct fdisk_parttype *sun_get_parttype( + struct fdisk_context *cxt, + size_t n) +{ + struct sun_disklabel *sunlabel = self_disklabel(cxt); + struct fdisk_parttype *t; + + if (n >= cxt->label->nparts_max) + return NULL; + + t = fdisk_label_get_parttype_from_code(cxt->label, + be16_to_cpu(sunlabel->vtoc.infos[n].id)); + return t ? : fdisk_new_unknown_parttype(be16_to_cpu(sunlabel->vtoc.infos[n].id), NULL); +} + + +static int sun_get_partition(struct fdisk_context *cxt, size_t n, + struct fdisk_partition *pa) +{ + struct sun_disklabel *sunlabel; + struct sun_partition *part; + uint16_t flags; + uint32_t start, len; + + assert(cxt); + assert(cxt->label); + assert(fdisk_is_label(cxt, SUN)); + + if (n >= cxt->label->nparts_max) + return -EINVAL; + + sunlabel = self_disklabel(cxt); + part = &sunlabel->partitions[n]; + + pa->used = part->num_sectors ? 1 : 0; + if (!pa->used) + return 0; + + flags = be16_to_cpu(sunlabel->vtoc.infos[n].flags); + start = be32_to_cpu(part->start_cylinder) + * cxt->geom.heads * cxt->geom.sectors; + len = be32_to_cpu(part->num_sectors); + + pa->type = sun_get_parttype(cxt, n); + if (pa->type && pa->type->code == SUN_TAG_WHOLEDISK) + pa->wholedisk = 1; + + if (flags & SUN_FLAG_UNMNT || flags & SUN_FLAG_RONLY) { + if (asprintf(&pa->attrs, "%c%c", + flags & SUN_FLAG_UNMNT ? 'u' : ' ', + flags & SUN_FLAG_RONLY ? 'r' : ' ') < 0) + return -ENOMEM; + } + + pa->start = start; + pa->size = len; + + return 0; +} + +/** + * fdisk_sun_set_alt_cyl: + * @cxt: context + * + * Sets number of alternative cylinders. This function uses libfdisk Ask API + * for dialog with user. + * + * Returns: 0 on success, <0 on error. + */ +int fdisk_sun_set_alt_cyl(struct fdisk_context *cxt) +{ + struct sun_disklabel *sunlabel = self_disklabel(cxt); + uintmax_t res; + int rc = fdisk_ask_number(cxt, 0, /* low */ + be16_to_cpu(sunlabel->acyl), /* default */ + 65535, /* high */ + _("Number of alternate cylinders"), /* query */ + &res); /* result */ + if (rc) + return rc; + + sunlabel->acyl = cpu_to_be16(res); + return 0; +} + +/** + * fdisk_sun_set_xcyl: + * @cxt: context + * + * Sets number of extra sectors per cylinder. This function uses libfdisk Ask API + * for dialog with user. + * + * Returns: 0 on success, <0 on error. + */ +int fdisk_sun_set_xcyl(struct fdisk_context *cxt) +{ + struct sun_disklabel *sunlabel = self_disklabel(cxt); + uintmax_t res; + int rc = fdisk_ask_number(cxt, 0, /* low */ + be16_to_cpu(sunlabel->apc), /* default */ + cxt->geom.sectors, /* high */ + _("Extra sectors per cylinder"), /* query */ + &res); /* result */ + if (rc) + return rc; + sunlabel->apc = cpu_to_be16(res); + return 0; +} + +/** + * fdisk_sun_set_ilfact: + * @cxt: context + * + * Sets interleave factor. This function uses libfdisk Ask API for dialog with + * user. + * + * Returns: 0 on success, <0 on error. + */ +int fdisk_sun_set_ilfact(struct fdisk_context *cxt) +{ + struct sun_disklabel *sunlabel = self_disklabel(cxt); + uintmax_t res; + int rc = fdisk_ask_number(cxt, 1, /* low */ + be16_to_cpu(sunlabel->intrlv), /* default */ + 32, /* high */ + _("Interleave factor"), /* query */ + &res); /* result */ + if (rc) + return rc; + sunlabel->intrlv = cpu_to_be16(res); + return 0; +} + +/** + * fdisk_sun_set_rspeed + * @cxt: context + * + * Sets rotation speed. This function uses libfdisk Ask API for dialog with + * user. + * + * Returns: 0 on success, <0 on error. + */ +int fdisk_sun_set_rspeed(struct fdisk_context *cxt) +{ + struct sun_disklabel *sunlabel = self_disklabel(cxt); + uintmax_t res; + int rc = fdisk_ask_number(cxt, 1, /* low */ + be16_to_cpu(sunlabel->rpm), /* default */ + USHRT_MAX, /* high */ + _("Rotation speed (rpm)"), /* query */ + &res); /* result */ + if (rc) + return rc; + sunlabel->rpm = cpu_to_be16(res); + return 0; +} + +/** + * fdisk_sun_set_pcylcount + * @cxt: context + * + * Sets number of physical cylinders. This function uses libfdisk Ask API for + * dialog with user. + * + * Returns: 0 on success, <0 on error. + */ +int fdisk_sun_set_pcylcount(struct fdisk_context *cxt) +{ + struct sun_disklabel *sunlabel = self_disklabel(cxt); + uintmax_t res; + int rc = fdisk_ask_number(cxt, 0, /* low */ + be16_to_cpu(sunlabel->pcyl), /* default */ + USHRT_MAX, /* high */ + _("Number of physical cylinders"), /* query */ + &res); /* result */ + if (!rc) + return rc; + sunlabel->pcyl = cpu_to_be16(res); + return 0; +} + +static int sun_write_disklabel(struct fdisk_context *cxt) +{ + struct sun_disklabel *sunlabel; + unsigned short *ush; + unsigned short csum = 0; + const size_t sz = sizeof(struct sun_disklabel); + + assert(cxt); + assert(cxt->label); + assert(fdisk_is_label(cxt, SUN)); + + sunlabel = self_disklabel(cxt); + + /* Maybe geometry has been modified */ + sunlabel->nhead = cpu_to_be16(cxt->geom.heads); + sunlabel->nsect = cpu_to_be16(cxt->geom.sectors); + + if (cxt->geom.cylinders != be16_to_cpu(sunlabel->ncyl)) + sunlabel->ncyl = cpu_to_be16( cxt->geom.cylinders + - be16_to_cpu(sunlabel->acyl) ); + + ush = (unsigned short *) sunlabel; + + while(ush < (unsigned short *)(&sunlabel->csum)) + csum ^= *ush++; + sunlabel->csum = csum; + if (lseek(cxt->dev_fd, 0, SEEK_SET) < 0) + return -errno; + if (write_all(cxt->dev_fd, sunlabel, sz) != 0) + return -errno; + + return 0; +} + +static int sun_set_partition( + struct fdisk_context *cxt, + size_t i, + struct fdisk_partition *pa) +{ + struct sun_disklabel *sunlabel; + struct sun_partition *part; + struct sun_info *info; + + assert(cxt); + assert(cxt->label); + assert(fdisk_is_label(cxt, SUN)); + + sunlabel = self_disklabel(cxt); + + if (i >= cxt->label->nparts_max) + return -EINVAL; + + if (pa->type) { + struct fdisk_parttype *t = pa->type; + + if (t->code > UINT16_MAX) + return -EINVAL; + + if (i == 2 && t->code != SUN_TAG_WHOLEDISK) + fdisk_info(cxt, _("Consider leaving partition 3 as Whole disk (5),\n" + "as SunOS/Solaris expects it and even Linux likes it.\n")); + + part = &sunlabel->partitions[i]; + info = &sunlabel->vtoc.infos[i]; + + if (cxt->script == NULL && + t->code == SUN_TAG_LINUX_SWAP && !part->start_cylinder) { + int yes, rc; + + rc = fdisk_ask_yesno(cxt, + _("It is highly recommended that the partition at offset 0\n" + "is UFS, EXT2FS filesystem or SunOS swap. Putting Linux swap\n" + "there may destroy your partition table and bootblock.\n" + "Are you sure you want to tag the partition as Linux swap?"), &yes); + if (rc) + return rc; + if (!yes) + return 1; + } + + switch (t->code) { + case SUN_TAG_SWAP: + case SUN_TAG_LINUX_SWAP: + /* swaps are not mountable by default */ + info->flags |= cpu_to_be16(SUN_FLAG_UNMNT); + break; + default: + /* assume other types are mountable; + user can change it anyway */ + info->flags &= ~cpu_to_be16(SUN_FLAG_UNMNT); + break; + } + info->id = cpu_to_be16(t->code); + } + + if (fdisk_partition_has_start(pa)) + sunlabel->partitions[i].start_cylinder = + cpu_to_be32(pa->start / (cxt->geom.heads * cxt->geom.sectors)); + if (fdisk_partition_has_size(pa)) + sunlabel->partitions[i].num_sectors = cpu_to_be32(pa->size); + + fdisk_label_set_changed(cxt->label, 1); + return 0; +} + + +static int sun_reset_alignment(struct fdisk_context *cxt __attribute__((__unused__))) +{ + return 0; +} + + +static int sun_partition_is_used( + struct fdisk_context *cxt, + size_t i) +{ + struct sun_disklabel *sunlabel; + + assert(cxt); + assert(cxt->label); + assert(fdisk_is_label(cxt, SUN)); + + if (i >= cxt->label->nparts_max) + return 0; + + sunlabel = self_disklabel(cxt); + return sunlabel->partitions[i].num_sectors ? 1 : 0; +} + +static const struct fdisk_field sun_fields[] = +{ + { FDISK_FIELD_DEVICE, N_("Device"), 10, 0 }, + { FDISK_FIELD_START, N_("Start"), 5, FDISK_FIELDFL_NUMBER }, + { FDISK_FIELD_END, N_("End"), 5, FDISK_FIELDFL_NUMBER }, + { FDISK_FIELD_SECTORS, N_("Sectors"), 5, FDISK_FIELDFL_NUMBER }, + { FDISK_FIELD_CYLINDERS,N_("Cylinders"), 5, FDISK_FIELDFL_NUMBER }, + { FDISK_FIELD_SIZE, N_("Size"), 5, FDISK_FIELDFL_NUMBER }, + { FDISK_FIELD_TYPEID, N_("Id"), 2, FDISK_FIELDFL_NUMBER }, + { FDISK_FIELD_TYPE, N_("Type"), 0.1, 0 }, + { FDISK_FIELD_ATTR, N_("Flags"), 0, FDISK_FIELDFL_NUMBER } +}; + +const struct fdisk_label_operations sun_operations = +{ + .probe = sun_probe_label, + .write = sun_write_disklabel, + .verify = sun_verify_disklabel, + .create = sun_create_disklabel, + .list = sun_list_disklabel, + + .get_part = sun_get_partition, + .set_part = sun_set_partition, + .add_part = sun_add_partition, + .del_part = sun_delete_partition, + + .part_is_used = sun_partition_is_used, + .part_toggle_flag = sun_toggle_partition_flag, + + .reset_alignment = sun_reset_alignment, +}; + +/* + * allocates SUN label driver + */ +struct fdisk_label *fdisk_new_sun_label(struct fdisk_context *cxt) +{ + struct fdisk_label *lb; + struct fdisk_sun_label *sun; + + assert(cxt); + + sun = calloc(1, sizeof(*sun)); + if (!sun) + return NULL; + + /* initialize generic part of the driver */ + lb = (struct fdisk_label *) sun; + lb->name = "sun"; + lb->id = FDISK_DISKLABEL_SUN; + lb->op = &sun_operations; + lb->parttypes = sun_parttypes; + lb->nparttypes = ARRAY_SIZE(sun_parttypes) - 1; + lb->fields = sun_fields; + lb->nfields = ARRAY_SIZE(sun_fields); + lb->flags |= FDISK_LABEL_FL_REQUIRE_GEOMETRY; + + return lb; +} diff --git a/libblkid/libfdisk/src/table.c b/libblkid/libfdisk/src/table.c new file mode 100644 index 000000000..1add09fca --- /dev/null +++ b/libblkid/libfdisk/src/table.c @@ -0,0 +1,664 @@ + +#include "fdiskP.h" + +/** + * SECTION: table + * @title: Table + * @short_description: container for fdisk partitions + * + * The fdisk_table is simple container for fdisk_partitions. The table is no + * directly connected to label data (partition table), and table changes don't + * affect in-memory or on-disk data. + */ + +/** + * fdisk_new_table: + * + * The table is a container for struct fdisk_partition entries. The container + * does not have any real connection with label (partition table) and with + * real on-disk data. + * + * Returns: newly allocated table struct. + */ +struct fdisk_table *fdisk_new_table(void) +{ + struct fdisk_table *tb = NULL; + + tb = calloc(1, sizeof(*tb)); + if (!tb) + return NULL; + + DBG(TAB, ul_debugobj(tb, "alloc")); + tb->refcount = 1; + INIT_LIST_HEAD(&tb->parts); + return tb; +} + +/** + * fdisk_reset_table: + * @tb: tab pointer + * + * Removes all entries (partitions) from the table. The parititons with zero + * reference count will be deallocated. This function does not modify partition + * table. + * + * Returns: 0 on success or negative number in case of error. + */ +int fdisk_reset_table(struct fdisk_table *tb) +{ + if (!tb) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "reset")); + + while (!list_empty(&tb->parts)) { + struct fdisk_partition *pa = list_entry(tb->parts.next, + struct fdisk_partition, parts); + fdisk_table_remove_partition(tb, pa); + } + + tb->nents = 0; + return 0; +} + +/** + * fdisk_ref_table: + * @tb: table pointer + * + * Incremparts reference counter. + */ +void fdisk_ref_table(struct fdisk_table *tb) +{ + if (tb) + tb->refcount++; +} + +/** + * fdisk_unref_table: + * @tb: table pointer + * + * De-incremparts reference counter, on zero the @tb is automatically + * deallocated. + */ +void fdisk_unref_table(struct fdisk_table *tb) +{ + if (!tb) + return; + + tb->refcount--; + if (tb->refcount <= 0) { + fdisk_reset_table(tb); + + DBG(TAB, ul_debugobj(tb, "free")); + free(tb); + } +} + +/** + * fdisk_table_is_empty: + * @tb: pointer to tab + * + * Returns: 1 if the table is without filesystems, or 0. + */ +int fdisk_table_is_empty(struct fdisk_table *tb) +{ + return tb == NULL || list_empty(&tb->parts) ? 1 : 0; +} + +/** + * fdisk_table_get_nents: + * @tb: pointer to tab + * + * Returns: number of entries in table. + */ +size_t fdisk_table_get_nents(struct fdisk_table *tb) +{ + return tb ? tb->nents : 0; +} + +/** + * fdisk_table_next_partition: + * @tb: tab pointer + * @itr: iterator + * @pa: returns the next tab entry + * + * Returns: 0 on success, negative number in case of error or 1 at the end of list. + * + * Example: + * <informalexample> + * <programlisting> + * while(fdisk_table_next_partition(tb, itr, &pa) == 0) { + * ... + * } + * </programlisting> + * </informalexample> + */ +int fdisk_table_next_partition( + struct fdisk_table *tb, + struct fdisk_iter *itr, + struct fdisk_partition **pa) +{ + int rc = 1; + + assert(tb); + assert(itr); + assert(pa); + + if (!tb || !itr || !pa) + return -EINVAL; + *pa = NULL; + + if (!itr->head) + FDISK_ITER_INIT(itr, &tb->parts); + if (itr->p != itr->head) { + FDISK_ITER_ITERATE(itr, *pa, struct fdisk_partition, parts); + rc = 0; + } + + return rc; +} + +struct fdisk_partition *fdisk_table_get_partition( + struct fdisk_table *tb, + size_t n) +{ + struct fdisk_partition *pa = NULL; + struct fdisk_iter itr; + + if (!tb) + return NULL; + + fdisk_reset_iter(&itr, FDISK_ITER_FORWARD); + + while (fdisk_table_next_partition(tb, &itr, &pa) == 0) { + if (n == 0) + return pa; + n--; + } + + return NULL; +} + +/** + * fdisk_table_add_partition + * @tb: tab pointer + * @pa: new entry + * + * Adds a new entry to table and increment @pa reference counter. Don't forget to + * use fdisk_unref_pa() after fdisk_table_add_partition() if you want to keep + * the @pa referenced by the table only. + * + * Returns: 0 on success or negative number in case of error. + */ +int fdisk_table_add_partition(struct fdisk_table *tb, struct fdisk_partition *pa) +{ + assert(tb); + assert(pa); + + if (!tb || !pa) + return -EINVAL; + + fdisk_ref_partition(pa); + list_add_tail(&pa->parts, &tb->parts); + tb->nents++; + + DBG(TAB, ul_debugobj(tb, "add entry %p [start=%ju, end=%ju, size=%ju, %s %s %s]", + pa, + (uintmax_t) fdisk_partition_get_start(pa), + (uintmax_t) fdisk_partition_get_end(pa), + (uintmax_t) fdisk_partition_get_size(pa), + fdisk_partition_is_freespace(pa) ? "freespace" : "", + fdisk_partition_is_nested(pa) ? "nested" : "", + fdisk_partition_is_container(pa) ? "container" : "primary")); + return 0; +} + +/* inserts @pa after @poz */ +static int table_insert_partition( + struct fdisk_table *tb, + struct fdisk_partition *poz, + struct fdisk_partition *pa) +{ + assert(tb); + assert(pa); + + fdisk_ref_partition(pa); + if (poz) + list_add(&pa->parts, &poz->parts); + else + list_add(&pa->parts, &tb->parts); + tb->nents++; + + DBG(TAB, ul_debugobj(tb, "insert entry %p pre=%p [start=%ju, end=%ju, size=%ju, %s %s %s]", + pa, poz ? poz : NULL, + (uintmax_t) fdisk_partition_get_start(pa), + (uintmax_t) fdisk_partition_get_end(pa), + (uintmax_t) fdisk_partition_get_size(pa), + fdisk_partition_is_freespace(pa) ? "freespace" : "", + fdisk_partition_is_nested(pa) ? "nested" : "", + fdisk_partition_is_container(pa) ? "container" : "")); + return 0; +} + +/** + * fdisk_table_remove_partition + * @tb: tab pointer + * @pa: new entry + * + * Removes the @pa from the table and de-increment reference counter of the @pa. The + * partition with zero reference counter will be deallocated. Don't forget to use + * fdisk_ref_partition() before call fdisk_table_remove_partition() if you want + * to use @pa later. + * + * Returns: 0 on success or negative number in case of error. + */ +int fdisk_table_remove_partition(struct fdisk_table *tb, struct fdisk_partition *pa) +{ + assert(tb); + assert(pa); + + if (!tb || !pa) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "remove entry %p", pa)); + list_del(&pa->parts); + INIT_LIST_HEAD(&pa->parts); + + fdisk_unref_partition(pa); + tb->nents--; + + return 0; +} + +/** + * fdisk_get_partitions + * @cxt: fdisk context + * @tb: returns table + * + * This function adds partitions from disklabel to @table, it allocates a new + * table if if @table points to NULL. + * + * Returns: 0 on success, otherwise, a corresponding error. + */ +int fdisk_get_partitions(struct fdisk_context *cxt, struct fdisk_table **tb) +{ + size_t i; + + if (!cxt || !cxt->label || !tb) + return -EINVAL; + if (!cxt->label->op->get_part) + return -ENOSYS; + + DBG(CXT, ul_debugobj(cxt, "get table")); + + if (!*tb && !(*tb = fdisk_new_table())) + return -ENOMEM; + + for (i = 0; i < cxt->label->nparts_max; i++) { + struct fdisk_partition *pa = NULL; + + if (fdisk_get_partition(cxt, i, &pa) != 0) + continue; + if (fdisk_partition_is_used(pa)) + fdisk_table_add_partition(*tb, pa); + fdisk_unref_partition(pa); + } + + return 0; +} + +static void debug_print_table(struct fdisk_table *tb) +{ + struct fdisk_iter itr; + struct fdisk_partition *pa; + + fdisk_reset_iter(&itr, FDISK_ITER_FORWARD); + while (fdisk_table_next_partition(tb, &itr, &pa) == 0) + ul_debugobj(tb, "partition %p [partno=%zu, start=%ju, end=%ju, size=%ju] ", + pa, pa->partno, + (uintmax_t) fdisk_partition_get_start(pa), + (uintmax_t) fdisk_partition_get_end(pa), + (uintmax_t) fdisk_partition_get_size(pa)); + +} + + +typedef int (*fdisk_partcmp_t)(struct fdisk_partition *, struct fdisk_partition *); + +static int cmp_parts_wrapper(struct list_head *a, struct list_head *b, void *data) +{ + struct fdisk_partition *pa = list_entry(a, struct fdisk_partition, parts), + *pb = list_entry(b, struct fdisk_partition, parts); + + fdisk_partcmp_t cmp = (fdisk_partcmp_t) data; + + return cmp(pa, pb); +} + + +/** + * fdisk_table_sort_partitions: + * @tb: table + * @cmp: compare function + * + * Sort partition in the table. + * + * Returns: 0 on success, <0 on error. + */ +int fdisk_table_sort_partitions(struct fdisk_table *tb, + int (*cmp)(struct fdisk_partition *, + struct fdisk_partition *)) +{ + if (!tb) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "Before sort:")); + ON_DBG(TAB, debug_print_table(tb)); + + list_sort(&tb->parts, cmp_parts_wrapper, (void *) cmp); + + DBG(TAB, ul_debugobj(tb, "After sort:")); + ON_DBG(TAB, debug_print_table(tb)); + + return 0; +} + +/* allocates a new freespace description */ +static int new_freespace(struct fdisk_context *cxt, + fdisk_sector_t start, + fdisk_sector_t end, + struct fdisk_partition *parent, + struct fdisk_partition **pa) +{ + assert(cxt); + assert(pa); + + *pa = NULL; + + if (start == end) + return 0; + *pa = fdisk_new_partition(); + if (!*pa) + return -ENOMEM; + + assert(start); + assert(end); + assert(end > start); + + (*pa)->freespace = 1; + (*pa)->start = fdisk_align_lba_in_range(cxt, start, start, end); + (*pa)->size = end - (*pa)->start + 1ULL; + + if (parent) + (*pa)->parent_partno = parent->partno; + return 0; +} + +/* add freespace description to the right place within @tb */ +static int table_add_freespace( + struct fdisk_context *cxt, + struct fdisk_table *tb, + fdisk_sector_t start, + fdisk_sector_t end, + struct fdisk_partition *parent) +{ + struct fdisk_partition *pa, *x, *real_parent = NULL, *best = NULL; + struct fdisk_iter itr; + int rc = 0; + + assert(tb); + + rc = new_freespace(cxt, start, end, parent, &pa); + if (rc) + return -ENOMEM; + if (!pa) + return 0; + + assert(fdisk_partition_has_start(pa)); + assert(fdisk_partition_has_end(pa)); + + DBG(TAB, ul_debugobj(tb, "adding freespace")); + + fdisk_reset_iter(&itr, FDISK_ITER_FORWARD); + if (parent && fdisk_partition_has_partno(parent)) { + while (fdisk_table_next_partition(tb, &itr, &x) == 0) { + if (!fdisk_partition_has_partno(x)) + continue; + if (x->partno == parent->partno) { + real_parent = x; + break; + } + } + if (!real_parent) { + DBG(TAB, ul_debugobj(tb, "not found freespace parent (partno=%zu)", + parent->partno)); + fdisk_reset_iter(&itr, FDISK_ITER_FORWARD); + } + } + + while (fdisk_table_next_partition(tb, &itr, &x) == 0) { + fdisk_sector_t end, best_end = 0; + + if (!fdisk_partition_has_end(x)) + continue; + + end = fdisk_partition_get_end(x); + if (best) + best_end = fdisk_partition_get_end(best); + + if (end < pa->start && (!best || best_end < end)) + best = x; + } + + if (!best && real_parent) + best = real_parent; + rc = table_insert_partition(tb, best, pa); + + fdisk_unref_partition(pa); + + DBG(TAB, ul_debugobj(tb, "adding freespace DONE [rc=%d]", rc)); + return rc; +} + +/* analyze @cont(ainer) in @parts and add all detected freespace into @tb, note + * that @parts has to be sorted by partition starts */ +static int check_container_freespace(struct fdisk_context *cxt, + struct fdisk_table *parts, + struct fdisk_table *tb, + struct fdisk_partition *cont) +{ + struct fdisk_iter itr; + struct fdisk_partition *pa; + fdisk_sector_t x, last, grain, lastplusoff; + int rc = 0; + + assert(cxt); + assert(parts); + assert(tb); + assert(cont); + assert(fdisk_partition_has_start(cont)); + + DBG(TAB, ul_debugobj(tb, "analyze container 0x%p", cont)); + + last = fdisk_partition_get_start(cont); + grain = cxt->grain > cxt->sector_size ? cxt->grain / cxt->sector_size : 1; + fdisk_reset_iter(&itr, FDISK_ITER_FORWARD); + + DBG(CXT, ul_debugobj(cxt, "initialized: last=%ju, grain=%ju", last, grain)); + + while (fdisk_table_next_partition(parts, &itr, &pa) == 0) { + + DBG(CXT, ul_debugobj(cxt, "partno=%zu, start=%ju", pa->partno, pa->start)); + + if (!pa->used || !fdisk_partition_is_nested(pa) + || !fdisk_partition_has_start(pa)) + continue; + + DBG(CXT, ul_debugobj(cxt, "freespace container analyze: partno=%zu, start=%ju, end=%ju", + pa->partno, + (uintmax_t) fdisk_partition_get_start(pa), + (uintmax_t) fdisk_partition_get_end(pa))); + + lastplusoff = last + cxt->first_lba; + if (pa->start > lastplusoff && pa->start - lastplusoff > grain) + rc = table_add_freespace(cxt, tb, lastplusoff, pa->start, cont); + if (rc) + goto done; + last = fdisk_partition_get_end(pa); + } + + /* free-space remaining in extended partition */ + x = fdisk_partition_get_start(cont) + fdisk_partition_get_size(cont) - 1; + lastplusoff = last + cxt->first_lba; + if (lastplusoff < x && x - lastplusoff > grain) { + DBG(TAB, ul_debugobj(tb, "add remaining space in container 0x%p", cont)); + rc = table_add_freespace(cxt, tb, lastplusoff, x, cont); + } + +done: + DBG(TAB, ul_debugobj(tb, "analyze container 0x%p DONE [rc=%d]", cont, rc)); + return rc; +} + + +/** + * fdisk_get_freespaces + * @cxt: fdisk context + * @tb: returns table + * + * This function adds freespace (described by fdisk_partition) to @table, it + * allocates a new table if the @table points to NULL. + * + * Note that free space smaller than grain (see fdisk_get_grain()) is ignored. + + * Returns: 0 on success, otherwise, a corresponding error. + */ +int fdisk_get_freespaces(struct fdisk_context *cxt, struct fdisk_table **tb) +{ + int rc = 0; + fdisk_sector_t last, grain; + struct fdisk_table *parts = NULL; + struct fdisk_partition *pa; + struct fdisk_iter itr; + + DBG(CXT, ul_debugobj(cxt, "get freespace")); + + if (!cxt || !cxt->label || !tb) + return -EINVAL; + if (!*tb && !(*tb = fdisk_new_table())) + return -ENOMEM; + + rc = fdisk_get_partitions(cxt, &parts); + if (rc) + goto done; + + fdisk_table_sort_partitions(parts, fdisk_partition_cmp_start); + fdisk_reset_iter(&itr, FDISK_ITER_FORWARD); + last = cxt->first_lba; + grain = cxt->grain > cxt->sector_size ? cxt->grain / cxt->sector_size : 1; + + DBG(CXT, ul_debugobj(cxt, "initialized: last=%ju, grain=%ju", last, grain)); + + /* analyze gaps between partitions */ + while (rc == 0 && fdisk_table_next_partition(parts, &itr, &pa) == 0) { + + DBG(CXT, ul_debugobj(cxt, "partno=%zu, start=%ju", pa->partno, pa->start)); + + if (!pa->used || pa->wholedisk || fdisk_partition_is_nested(pa) + || !fdisk_partition_has_start(pa)) + continue; + DBG(CXT, ul_debugobj(cxt, "freespace analyze: partno=%zu, start=%ju, end=%ju", + pa->partno, + (uintmax_t) fdisk_partition_get_start(pa), + (uintmax_t) fdisk_partition_get_end(pa))); + if (last + grain <= pa->start) { + rc = table_add_freespace(cxt, *tb, + last + (last > cxt->first_lba ? 1 : 0), + pa->start - 1, NULL); + } + /* add gaps between logical partitions */ + if (fdisk_partition_is_container(pa)) + rc = check_container_freespace(cxt, parts, *tb, pa); + last = fdisk_partition_get_end(pa); + } + + /* add free-space behind last partition to the end of the table (so + * don't use table_add_freespace()) */ + if (rc == 0 && last + grain < cxt->total_sectors - 1) { + DBG(CXT, ul_debugobj(cxt, "freespace behind last partition detected")); + rc = new_freespace(cxt, + last + (last > cxt->first_lba ? 1 : 0), + cxt->last_lba, NULL, &pa); + if (pa) { + fdisk_table_add_partition(*tb, pa); + fdisk_unref_partition(pa); + } + } + +done: + fdisk_unref_table(parts); + + DBG(CXT, ul_debugobj(cxt, "get freespace DONE [rc=%d]", rc)); + return rc; +} + +/** + * fdisk_table_wrong_order: + * @tb: table + * + * Returns: 1 of the table is not in disk order + */ +int fdisk_table_wrong_order(struct fdisk_table *tb) +{ + struct fdisk_partition *pa; + struct fdisk_iter itr; + fdisk_sector_t last = 0; + + DBG(TAB, ul_debugobj(tb, "wrong older check")); + + fdisk_reset_iter(&itr, FDISK_ITER_FORWARD); + while (tb && fdisk_table_next_partition(tb, &itr, &pa) == 0) { + if (!fdisk_partition_has_start(pa)) + continue; + if (pa->start < last) + return 1; + last = pa->start; + } + return 0; +} + +/** + * fdisk_apply_table: + * @cxt: context + * @tb: table + * + * Add partitions from table @tb to the in-memory disk label. See + * fdisk_add_partition(), fdisk_delete_all_partitions(). The partitons + * that does not define start (or does not follow the default start) + * are ingored. + * + * Returns: 0 on success, <0 on error. + */ +int fdisk_apply_table(struct fdisk_context *cxt, struct fdisk_table *tb) +{ + struct fdisk_partition *pa; + struct fdisk_iter itr; + int rc = 0; + + assert(cxt); + assert(tb); + + DBG(TAB, ul_debugobj(tb, "applying to context %p", cxt)); + + fdisk_reset_iter(&itr, FDISK_ITER_FORWARD); + while (tb && fdisk_table_next_partition(tb, &itr, &pa) == 0) { + if (!fdisk_partition_has_start(pa) && !pa->start_follow_default) + continue; + rc = fdisk_add_partition(cxt, pa, NULL); + if (rc) + break; + } + + return rc; +} + diff --git a/libblkid/libfdisk/src/test.c b/libblkid/libfdisk/src/test.c new file mode 100644 index 000000000..31ed7e03a --- /dev/null +++ b/libblkid/libfdisk/src/test.c @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2008-2009 Karel Zak <kzak@redhat.com> + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + * + * Routines for TEST_PROGRAMs + */ + +#include <stdlib.h> +#include <string.h> +#include <errno.h> + +#ifndef TEST_PROGRAM +#define TEST_PROGRAM +#endif + +#include "fdiskP.h" + +int fdisk_run_test(struct fdisk_test *tests, int argc, char *argv[]) +{ + int rc = -1; + struct fdisk_test *ts; + + assert(tests); + assert(argc); + assert(argv); + + if (argc < 2 || + strcmp(argv[1], "--help") == 0 || + strcmp(argv[1], "-h") == 0) + goto usage; + + fdisk_init_debug(0); + + for (ts = tests; ts->name; ts++) { + if (strcmp(ts->name, argv[1]) == 0) { + rc = ts->body(ts, argc - 1, argv + 1); + if (rc) + printf("FAILED [rc=%d]", rc); + break; + } + } + + if (rc < 0 && ts->name == NULL) + goto usage; + + return rc == 0 ? EXIT_SUCCESS : EXIT_FAILURE; +usage: + printf("\nUsage:\n\t%s <test> [testoptions]\nTests:\n", + program_invocation_short_name); + for (ts = tests; ts->name; ts++) { + printf("\t%-15s", ts->name); + if (ts->usage) + printf(" %s\n", ts->usage); + } + printf("\n"); + return EXIT_FAILURE; +} diff --git a/libblkid/libfdisk/src/utils.c b/libblkid/libfdisk/src/utils.c new file mode 100644 index 000000000..482a3062d --- /dev/null +++ b/libblkid/libfdisk/src/utils.c @@ -0,0 +1,154 @@ + +#include "fdiskP.h" +#include "pathnames.h" + +#include <ctype.h> + +/** + * SECTION: utils + * @title: Utils + * @short_description: misc fdisk functions + */ + +/* + * Zeros in-memory first sector buffer + */ +int fdisk_init_firstsector_buffer(struct fdisk_context *cxt) +{ + if (!cxt) + return -EINVAL; + + if (!cxt->firstsector || cxt->firstsector_bufsz != cxt->sector_size) { + /* Let's allocate a new buffer if no allocated yet, or the + * current buffer has incorrect size */ + if (!cxt->parent || cxt->parent->firstsector != cxt->firstsector) + free(cxt->firstsector); + + DBG(CXT, ul_debugobj(cxt, "initialize in-memory first sector " + "buffer [sector_size=%lu]", cxt->sector_size)); + cxt->firstsector = calloc(1, cxt->sector_size); + if (!cxt->firstsector) + return -ENOMEM; + + cxt->firstsector_bufsz = cxt->sector_size; + return 0; + } + + DBG(CXT, ul_debugobj(cxt, "zeroize in-memory first sector buffer")); + memset(cxt->firstsector, 0, cxt->firstsector_bufsz); + return 0; +} + +int fdisk_read_firstsector(struct fdisk_context *cxt) +{ + ssize_t r; + int rc; + + assert(cxt); + assert(cxt->sector_size); + + rc = fdisk_init_firstsector_buffer(cxt); + if (rc) + return rc; + + assert(cxt->sector_size == cxt->firstsector_bufsz); + + DBG(CXT, ul_debugobj(cxt, "reading first sector " + "buffer [sector_size=%lu]", cxt->sector_size)); + + r = lseek(cxt->dev_fd, 0, SEEK_SET); + if (r == -1) + { + DBG(CXT, ul_debugobj(cxt, "failed to seek to first sector %m")); + return -errno; + } + + r = read(cxt->dev_fd, cxt->firstsector, cxt->sector_size); + + if (r != cxt->sector_size) { + if (!errno) + errno = EINVAL; /* probably too small file/device */ + DBG(CXT, ul_debugobj(cxt, "failed to read first sector %m")); + return -errno; + } + + return 0; +} + +/** + * fdisk_partname: + * @dev: device name + * @partno: partition name + * + * Return: allocated buffer with partition name, use free() to deallocate. + */ +char *fdisk_partname(const char *dev, size_t partno) +{ + char *res = NULL; + const char *p = ""; + int w = 0; + + if (!dev || !*dev) { + if (asprintf(&res, "%zd", partno) > 0) + return res; + return NULL; + } + + w = strlen(dev); + if (isdigit(dev[w - 1])) +#ifdef __GNU__ + p = "s"; +#else + p = "p"; +#endif + + /* devfs kludge - note: fdisk partition names are not supposed + to equal kernel names, so there is no reason to do this */ + if (strcmp(dev + w - 4, "disc") == 0) { + w -= 4; + p = "part"; + } + + /* udev names partitions by appending -partN + e.g. ata-SAMSUNG_SV8004H_0357J1FT712448-part1 */ + if ((strncmp(dev, _PATH_DEV_BYID, sizeof(_PATH_DEV_BYID) - 1) == 0) || + strncmp(dev, _PATH_DEV_BYPATH, sizeof(_PATH_DEV_BYPATH) - 1) == 0) { + p = "-part"; + } + + if (asprintf(&res, "%.*s%s%zu", w, dev, p, partno) > 0) + return res; + + return NULL; +} + +#ifdef TEST_PROGRAM +struct fdisk_label *fdisk_new_dos_label(struct fdisk_context *cxt) { return NULL; } +struct fdisk_label *fdisk_new_bsd_label(struct fdisk_context *cxt) { return NULL; } + +int test_partnames(struct fdisk_test *ts, int argc, char *argv[]) +{ + size_t i; + const char *disk = argv[1]; + + for (i = 0; i < 5; i++) { + char *p = fdisk_partname(disk, i + 1); + if (p) + printf("%zu: '%s'\n", i + 1, p); + free(p); + } + + return 0; +} + +int main(int argc, char *argv[]) +{ + struct fdisk_test tss[] = { + { "--partnames", test_partnames, "<diskname>" }, + { NULL } + }; + + return fdisk_run_test(tss, argc, argv); +} + +#endif |