#ifdef HAVE_LIBBLKID #include #endif #include "blkdev.h" #include "fdiskP.h" /* * Alignment according to logical granulity (usually 1MiB) */ static int lba_is_aligned(struct fdisk_context *cxt, 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, 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)); } /* * Align @lba in @direction FDISK_ALIGN_{UP,DOWN,NEAREST} */ sector_t fdisk_align_lba(struct fdisk_context *cxt, sector_t lba, int direction) { sector_t res; if (lba_is_aligned(cxt, lba)) res = lba; else { 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(TOPOLOGY, dbgprint("LBA %ju -aligned-to-> %ju", (uintmax_t) lba, (uintmax_t) res)); return res; } /* * Align @lba, the result has to be between @start and @stop */ sector_t fdisk_align_lba_in_range(struct fdisk_context *cxt, sector_t lba, sector_t start, sector_t stop) { 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) return start; else if (lba > stop) return stop; return lba; } /* * Print warning if the partition @lba (start of the @partition) is not * aligned to physical sector boundary. */ void fdisk_warn_alignment(struct fdisk_context *cxt, sector_t lba, int partition) { if (!lba_is_phy_aligned(cxt, lba)) printf(_("Partition %i does not start on physical sector boundary.\n"), partition + 1); } 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 autodiscovery and apply user specified geometry. The function * fdisk_reset_device_properties() restores the original setting. * * 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(GEOMETRY, dbgprint("override C/H/S: %u/%u/%u", (unsigned) cxt->geom.cylinders, (unsigned) cxt->geom.heads, (unsigned) cxt->geom.sectors)); return 0; } 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(GEOMETRY, dbgprint("user C/H/S: %u/%u/%u", (unsigned) cxt->user_geom.cylinders, (unsigned) cxt->user_geom.heads, (unsigned) cxt->user_geom.sectors)); return 0; } int fdisk_save_user_sector_size(struct fdisk_context *cxt, unsigned int phy, unsigned int log) { if (!cxt) return -EINVAL; DBG(TOPOLOGY, dbgprint("user phy/log sector size: %u/%u", phy, log)); cxt->user_pyh_sector = phy; cxt->user_log_sector = log; return 0; } 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(TOPOLOGY, dbgprint("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); DBG(GEOMETRY, dbgprint("new C/H/S: %u/%u/%u", (unsigned) cxt->geom.cylinders, (unsigned) cxt->geom.heads, (unsigned) cxt->geom.sectors)); DBG(TOPOLOGY, dbgprint("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->total_sectors = 0; memset(&cxt->geom, 0, sizeof(struct fdisk_geometry)); } int fdisk_reset_device_properties(struct fdisk_context *cxt) { int rc; if (!cxt) return -EINVAL; DBG(TOPOLOGY, dbgprint("*** 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) { sector_t nsects; assert(cxt); assert(cxt->geom.heads == 0); DBG(GEOMETRY, dbgprint("%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, &nsects)) cxt->total_sectors = (nsects / (cxt->sector_size >> 9)); /* 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(GEOMETRY, dbgprint("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(TOPOLOGY, dbgprint("%s: discovering topology...", cxt->dev_path)); #ifdef HAVE_LIBBLKID DBG(TOPOLOGY, dbgprint("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(TOPOLOGY, dbgprint("result: log/phy sector size: %ld/%ld", cxt->sector_size, cxt->phy_sector_size)); DBG(TOPOLOGY, dbgprint("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. */ sector_t fdisk_topology_get_first_lba(struct fdisk_context *cxt) { 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; } /* * The LBA of the first partition is based on the device geometry and topology. * This offset is generic generic (and recommended) for all labels. * * Returns: 0 on error or number of bytes. */ unsigned long fdisk_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 or label specific values. * * 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(TOPOLOGY, dbgprint("reseting alignment...")); /* default */ cxt->grain = fdisk_topology_get_grain(cxt); cxt->first_lba = fdisk_topology_get_first_lba(cxt); /* overwrite default by label stuff */ if (cxt->label && cxt->label->op->reset_alignment) rc = cxt->label->op->reset_alignment(cxt); DBG(TOPOLOGY, dbgprint("%s alignment reseted to: " "first LBA=%ju, grain=%lu [rc=%d]", cxt->label ? cxt->label->name : NULL, (uintmax_t) cxt->first_lba, cxt->grain, rc)); return rc; } sector_t fdisk_scround(struct fdisk_context *cxt, sector_t num) { sector_t un = fdisk_context_get_units_per_sector(cxt); return (num + un - 1) / un; } sector_t fdisk_cround(struct fdisk_context *cxt, sector_t num) { return fdisk_context_use_cylinders(cxt) ? (num / fdisk_context_get_units_per_sector(cxt)) + 1 : num; } 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; }