summaryrefslogtreecommitdiffstats
path: root/libparted/unit.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--libparted/unit.c586
1 files changed, 586 insertions, 0 deletions
diff --git a/libparted/unit.c b/libparted/unit.c
new file mode 100644
index 0000000..a63b98d
--- /dev/null
+++ b/libparted/unit.c
@@ -0,0 +1,586 @@
+/*
+ libparted - a library for manipulating disk partitions
+ Copyright (C) 2005, 2007, 2009-2014, 2019-2023 Free Software Foundation,
+ Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program 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 General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/** \file unit.c */
+
+/**
+ * \addtogroup PedUnit
+ *
+ * \brief The PedUnit module provides a standard mechanism for describing
+ * and parsing locations within devices in human-friendly plain text.
+ *
+ * Internally, libparted uses PedSector (which is typedef'ed to be long long
+ * in <parted/device.h>) to describe device locations such as the start and
+ * end of partitions. However, sector numbers are often long and unintuitive.
+ * For example, my extended partition starts at sector 208845. PedUnit allows
+ * this location to be represented in more intutitive ways, including "106Mb",
+ * "0Gb" and "0%", as well as "208845s". PedUnit aims to provide facilities
+ * to provide a consistent system for describing device locations all
+ * throughout libparted.
+ *
+ * PedUnit provides two basic services: converting a PedSector into a text
+ * representation, and parsing a text representation into a PedSector.
+ * PedUnit currently supports these units:
+ *
+ * sectors, bytes, kilobytes, megabytes, gigabytes, terabytes, compact,
+ * cylinder and percent.
+ *
+ * PedUnit has a global variable that contains the default unit for all
+ * conversions.
+ *
+ * @{
+ */
+
+
+
+
+#include <config.h>
+#include <parted/parted.h>
+#include <parted/debug.h>
+#include <parted/unit.h>
+
+#include <ctype.h>
+#include <stdio.h>
+#include <float.h>
+
+#define N_(String) String
+#if ENABLE_NLS
+# include <libintl.h>
+# define _(String) dgettext (PACKAGE, String)
+#else
+# define _(String) (String)
+#endif /* ENABLE_NLS */
+
+
+static PedUnit default_unit = PED_UNIT_COMPACT;
+static const char* unit_names[] = {
+ "s",
+ "B",
+ "kB",
+ "MB",
+ "GB",
+ "TB",
+ "compact",
+ "cyl",
+ "chs",
+ "%",
+ "kiB",
+ "MiB",
+ "GiB",
+ "TiB"
+};
+
+
+/**
+ * \brief Set the default \p unit used by subsequent calls to the PedUnit API.
+ *
+ * In particular, this affects how locations inside error messages
+ * (exceptions) are displayed.
+ */
+void
+ped_unit_set_default (PedUnit unit)
+{
+ default_unit = unit;
+}
+
+
+/**
+ * \brief Get the current default unit.
+ */
+PedUnit _GL_ATTRIBUTE_PURE
+ped_unit_get_default ()
+{
+ return default_unit;
+}
+
+/**
+ * Get the byte size of a given \p unit.
+ */
+long long
+ped_unit_get_size (const PedDevice* dev, PedUnit unit)
+{
+ PedSector cyl_size = dev->bios_geom.heads * dev->bios_geom.sectors;
+
+ switch (unit) {
+ case PED_UNIT_SECTOR: return dev->sector_size;
+ case PED_UNIT_BYTE: return 1;
+ case PED_UNIT_KILOBYTE: return PED_KILOBYTE_SIZE;
+ case PED_UNIT_MEGABYTE: return PED_MEGABYTE_SIZE;
+ case PED_UNIT_GIGABYTE: return PED_GIGABYTE_SIZE;
+ case PED_UNIT_TERABYTE: return PED_TERABYTE_SIZE;
+ case PED_UNIT_KIBIBYTE: return PED_KIBIBYTE_SIZE;
+ case PED_UNIT_MEBIBYTE: return PED_MEBIBYTE_SIZE;
+ case PED_UNIT_GIBIBYTE: return PED_GIBIBYTE_SIZE;
+ case PED_UNIT_TEBIBYTE: return PED_TEBIBYTE_SIZE;
+ case PED_UNIT_CYLINDER: return cyl_size * dev->sector_size;
+ case PED_UNIT_CHS: return dev->sector_size;
+
+ case PED_UNIT_PERCENT:
+ return dev->length * dev->sector_size / 100;
+
+ case PED_UNIT_COMPACT:
+ ped_exception_throw (
+ PED_EXCEPTION_ERROR, PED_EXCEPTION_CANCEL,
+ _("Cannot get unit size for special unit "
+ "'COMPACT'."));
+ return 0;
+ }
+
+ /* never reached */
+ PED_ASSERT(0);
+ return 0;
+}
+
+/**
+ * Get a textual (non-internationalized) representation of a \p unit.
+ *
+ * For example, the textual representation of PED_UNIT_SECTOR is "s".
+ */
+const char*
+ped_unit_get_name (PedUnit unit)
+{
+ return unit_names[unit];
+}
+
+/**
+ * Get a unit based on its textual representation: \p unit_name.
+ *
+ * For example, ped_unit_get_by_name("Mb") returns PED_UNIT_MEGABYTE.
+ */
+PedUnit
+ped_unit_get_by_name (const char* unit_name)
+{
+ PedUnit unit;
+ for (unit = PED_UNIT_FIRST; unit <= PED_UNIT_LAST; unit++) {
+ if (!strcasecmp (unit_names[unit], unit_name))
+ return unit;
+ }
+ return -1;
+}
+
+static char*
+ped_strdup (const char *str)
+{
+ char *result;
+ result = ped_malloc (strlen (str) + 1);
+ if (!result)
+ return NULL;
+ strcpy (result, str);
+ return result;
+}
+
+/**
+ * \brief Get a string that describes the location of the \p byte on
+ * device \p dev.
+ *
+ * The string is described with the desired \p unit.
+ * The returned string must be freed with free().
+ */
+char*
+ped_unit_format_custom_byte (const PedDevice* dev, PedSector byte, PedUnit unit)
+{
+ char buf[100];
+ PedSector sector = byte / dev->sector_size;
+ double d, w;
+ int p;
+
+ PED_ASSERT (dev != NULL);
+
+ /* CHS has a special comma-separated format. */
+ if (unit == PED_UNIT_CHS) {
+ const PedCHSGeometry *chs = &dev->bios_geom;
+ snprintf (buf, 100, "%lld,%lld,%lld",
+ sector / chs->sectors / chs->heads,
+ (sector / chs->sectors) % chs->heads,
+ sector % chs->sectors);
+ return ped_strdup (buf);
+ }
+
+ /* Cylinders, sectors and bytes should be rounded down... */
+ if (unit == PED_UNIT_CYLINDER
+ || unit == PED_UNIT_SECTOR
+ || unit == PED_UNIT_BYTE) {
+ snprintf (buf, 100, "%lld%s",
+ byte / ped_unit_get_size (dev, unit),
+ ped_unit_get_name (unit));
+ return ped_strdup (buf);
+ }
+
+ if (unit == PED_UNIT_COMPACT) {
+ if (byte >= 10LL * PED_TERABYTE_SIZE)
+ unit = PED_UNIT_TERABYTE;
+ else if (byte >= 10LL * PED_GIGABYTE_SIZE)
+ unit = PED_UNIT_GIGABYTE;
+ else if (byte >= 10LL * PED_MEGABYTE_SIZE)
+ unit = PED_UNIT_MEGABYTE;
+ else if (byte >= 10LL * PED_KILOBYTE_SIZE)
+ unit = PED_UNIT_KILOBYTE;
+ else
+ unit = PED_UNIT_BYTE;
+ }
+
+ /* IEEE754 says that 100.5 has to be rounded to 100 (by printf) */
+ /* but 101.5 has to be rounded to 102... so we multiply by 1+E. */
+ /* This just divide by 2 the natural IEEE754 extended precision */
+ /* and won't cause any trouble before 1000 TB */
+ d = ((double)byte / ped_unit_get_size (dev, unit))
+ * (1. + DBL_EPSILON);
+ w = d + ( (d < 10. ) ? 0.005 :
+ (d < 100.) ? 0.05 :
+ 0.5 );
+ p = (w < 10. ) ? 2 :
+ (w < 100.) ? 1 :
+ 0 ;
+
+#ifdef __BEOS__
+ snprintf (buf, 100, "%.*f%s", p, d, ped_unit_get_name(unit));
+#else
+ snprintf (buf, 100, "%1$.*2$f%3$s", d, p, ped_unit_get_name (unit));
+#endif
+
+ return ped_strdup (buf);
+}
+
+/**
+ * \brief Get a string that describes the location of the \p byte on
+ * device \p dev.
+ *
+ * The string is described with the default unit, which is set
+ * by ped_unit_set_default().
+ * The returned string must be freed with free().
+ */
+char*
+ped_unit_format_byte (const PedDevice* dev, PedSector byte)
+{
+ PED_ASSERT (dev != NULL);
+ return ped_unit_format_custom_byte (dev, byte, default_unit);
+}
+
+/**
+ * \brief Get a string that describes the location \p sector on device \p dev.
+ *
+ * The string is described with the desired \p unit.
+ * The returned string must be freed with free().
+ */
+char*
+ped_unit_format_custom (const PedDevice* dev, PedSector sector, PedUnit unit)
+{
+ PED_ASSERT (dev != NULL);
+ return ped_unit_format_custom_byte(dev, sector*dev->sector_size, unit);
+}
+
+/**
+ * \brief Get a string that describes the location \p sector on device \p dev.
+ *
+ * The string is described with the default unit, which is set
+ * by ped_unit_set_default().
+ * The returned string must be freed with free().
+ */
+char*
+ped_unit_format (const PedDevice* dev, PedSector sector)
+{
+ PED_ASSERT (dev != NULL);
+ return ped_unit_format_custom_byte (dev, sector * dev->sector_size,
+ default_unit);
+}
+
+/**
+ * If \p str contains a valid description of a location on \p dev,
+ * then \p *sector is modified to describe the location and a geometry
+ * is created in \p *range describing a 2 units large area centered on
+ * \p *sector. If the \p range as described here would be partially outside
+ * the device \p dev, the geometry returned is the intersection between the
+ * former and the whole device geometry. If no units are specified, then the
+ * default unit is assumed.
+ *
+ * \return \c 1 if \p str is a valid location description, \c 0 otherwise
+ */
+int
+ped_unit_parse (const char* str, const PedDevice* dev, PedSector *sector,
+ PedGeometry** range)
+{
+ return ped_unit_parse_custom (str, dev, default_unit, sector, range);
+}
+
+/* Inefficiently removes all spaces from a string, in-place. */
+static void
+strip_string (char* str)
+{
+ int i;
+
+ for (i = 0; str[i] != 0; i++) {
+ if (isspace (str[i])) {
+ int j;
+ for (j = i + 1; str[j] != 0; j++)
+ str[j - 1] = str[j];
+ }
+ }
+}
+
+
+/* Find non-number suffix. Eg: find_suffix("32Mb") returns a pointer to
+ * "Mb". */
+static char* _GL_ATTRIBUTE_PURE
+find_suffix (const char* str)
+{
+ while (str[0] != 0 && (isdigit (str[0]) || strchr(",.-", str[0])))
+ str++;
+ return (char *) str;
+}
+
+static void
+remove_punct (char* str)
+{
+ int i = 0;
+
+ for (i = 0; str[i]; i++) {
+ if (ispunct (str[i]))
+ str[i] = ' ';
+ }
+}
+
+static int _GL_ATTRIBUTE_PURE
+is_chs (const char* str)
+{
+ int punct_count = 0;
+ int i = 0;
+
+ for (i = 0; str[i]; i++)
+ punct_count += ispunct (str[i]) != 0;
+ return punct_count == 2;
+}
+
+static int
+parse_chs (const char* str, const PedDevice* dev, PedSector* sector,
+ PedGeometry** range)
+{
+ PedSector cyl_size = dev->bios_geom.heads * dev->bios_geom.sectors;
+ PedCHSGeometry chs;
+
+ char* copy = ped_strdup (str);
+ if (!copy)
+ return 0;
+ strip_string (copy);
+ remove_punct (copy);
+
+ if (sscanf (copy, "%d %d %d",
+ &chs.cylinders, &chs.heads, &chs.sectors) != 3) {
+ ped_exception_throw (
+ PED_EXCEPTION_ERROR, PED_EXCEPTION_CANCEL,
+ _("\"%s\" has invalid syntax for locations."),
+ copy);
+ goto error_free_copy;
+ }
+
+ if (chs.heads >= dev->bios_geom.heads) {
+ ped_exception_throw (
+ PED_EXCEPTION_ERROR, PED_EXCEPTION_CANCEL,
+ _("The maximum head value is %d."),
+ dev->bios_geom.heads - 1);
+ goto error_free_copy;
+ }
+ if (chs.sectors >= dev->bios_geom.sectors) {
+ ped_exception_throw (
+ PED_EXCEPTION_ERROR, PED_EXCEPTION_CANCEL,
+ _("The maximum sector value is %d."),
+ dev->bios_geom.sectors - 1);
+ goto error_free_copy;
+ }
+
+ *sector = 1LL * chs.cylinders * cyl_size
+ + chs.heads * dev->bios_geom.sectors
+ + chs.sectors;
+
+ if (*sector >= dev->length) {
+ ped_exception_throw (
+ PED_EXCEPTION_ERROR, PED_EXCEPTION_CANCEL,
+ _("The location %s is outside of the "
+ "device %s."),
+ str, dev->path);
+ goto error_free_copy;
+ }
+ if (range)
+ *range = ped_geometry_new (dev, *sector, 1);
+ free (copy);
+ return !range || *range != NULL;
+
+error_free_copy:
+ free (copy);
+ *sector = 0;
+ if (range)
+ *range = NULL;
+ return 0;
+}
+
+static PedSector
+clip (const PedDevice* dev, PedSector sector)
+{
+ if (sector < 0)
+ return 0;
+ if (sector > dev->length - 1)
+ return dev->length - 1;
+ return sector;
+}
+
+static PedGeometry*
+geometry_from_centre_radius (const PedDevice* dev,
+ PedSector sector, PedSector radius)
+{
+ PedSector start = clip (dev, sector - radius);
+ PedSector end = clip (dev, sector + radius);
+ if (sector - end > radius || start - sector > radius)
+ return NULL;
+ return ped_geometry_new (dev, start, end - start + 1);
+}
+
+static PedUnit
+parse_unit_suffix (const char* suffix, PedUnit suggested_unit)
+{
+ if (strlen (suffix) > 1 && tolower (suffix[1]) == 'i') {
+ switch (tolower (suffix[0])) {
+ case 'k': return PED_UNIT_KIBIBYTE;
+ case 'm': return PED_UNIT_MEBIBYTE;
+ case 'g': return PED_UNIT_GIBIBYTE;
+ case 't': return PED_UNIT_TEBIBYTE;
+ }
+ } else if (strlen (suffix) > 0) {
+ switch (tolower (suffix[0])) {
+ case 's': return PED_UNIT_SECTOR;
+ case 'b': return PED_UNIT_BYTE;
+ case 'k': return PED_UNIT_KILOBYTE;
+ case 'm': return PED_UNIT_MEGABYTE;
+ case 'g': return PED_UNIT_GIGABYTE;
+ case 't': return PED_UNIT_TERABYTE;
+ case 'c': return PED_UNIT_CYLINDER;
+ case '%': return PED_UNIT_PERCENT;
+ }
+ }
+
+ if (suggested_unit == PED_UNIT_COMPACT) {
+ if (default_unit == PED_UNIT_COMPACT)
+ return PED_UNIT_MEGABYTE;
+ else
+ return default_unit;
+ }
+
+ return suggested_unit;
+}
+
+/**
+ * If \p str contains a valid description of a location on \p dev, then
+ * \p *sector is modified to describe the location and a geometry is created
+ * in \p *range describing a 2 units large area centered on \p *sector. If the
+ * \p range as described here would be partially outside the device \p dev, the
+ * geometry returned is the intersection between the former and the whole
+ * device geometry. If no units are specified, then the default unit is
+ * assumed.
+ *
+ * \throws PED_EXCEPTION_ERROR if \p str contains invalid description of a
+ * location
+ * \throws PED_EXCEPTION_ERROR if location described by \p str
+ * is outside of the device \p dev->path
+ *
+ * \return \c 1 if \p str is a valid location description, \c 0 otherwise.
+ */
+int
+ped_unit_parse_custom (const char* str, const PedDevice* dev, PedUnit unit,
+ PedSector* sector, PedGeometry** range)
+{
+ char* copy;
+ char* suffix;
+ double num;
+ long long unit_size;
+ PedSector radius;
+
+ if (is_chs (str))
+ return parse_chs (str, dev, sector, range);
+
+ copy = ped_strdup (str);
+ if (!copy)
+ goto error;
+ strip_string (copy);
+
+ suffix = find_suffix (copy);
+ unit = parse_unit_suffix (suffix, unit);
+ suffix[0] = 0;
+
+ if (sscanf (copy, "%lf", &num) != 1) {
+ ped_exception_throw (
+ PED_EXCEPTION_ERROR,
+ PED_EXCEPTION_CANCEL,
+ _("Invalid number."));
+ goto error_free_copy;
+ }
+ if (num > 0 && num < 1) {
+ ped_exception_throw (
+ PED_EXCEPTION_ERROR, PED_EXCEPTION_CANCEL,
+ _("Use a smaller unit instead of a value < 1"));
+ goto error_free_copy;
+ }
+
+ unit_size = ped_unit_get_size (dev, unit);
+ switch (unit) {
+ /* If the user specifies the address using IEC units e.g., 4MiB, as in
+ parted -s -- $dev mklabel gpt mkpart P-NAME 4MiB -34s
+ do not use size of the unit as the range. Rather, presume that they
+ are specifying precisely the starting or ending number,
+ and treat "4MiB" just as we would treat "4194304B". */
+ case PED_UNIT_KIBIBYTE:
+ case PED_UNIT_MEBIBYTE:
+ case PED_UNIT_GIBIBYTE:
+ case PED_UNIT_TEBIBYTE:
+ radius = 0;
+ break;
+ default:
+ radius = (ped_div_round_up (unit_size, dev->sector_size) / 2) - 1;
+ if (radius < 0)
+ radius = 0;
+ }
+
+ *sector = num * unit_size / dev->sector_size;
+ /* negative numbers count from the end */
+ if (copy[0] == '-')
+ *sector += dev->length;
+ if (range) {
+ *range = geometry_from_centre_radius (dev, *sector, radius);
+ if (!*range) {
+ ped_exception_throw (
+ PED_EXCEPTION_ERROR, PED_EXCEPTION_CANCEL,
+ _("The location %s is outside of the "
+ "device %s."),
+ str, dev->path);
+ goto error_free_copy;
+ }
+ }
+ *sector = clip (dev, *sector);
+
+ free (copy);
+ return 1;
+
+error_free_copy:
+ free (copy);
+error:
+ *sector = 0;
+ if (range)
+ *range = NULL;
+ return 0;
+}
+
+
+/** @} */