/* libparted - a library for manipulating disk partitions Copyright (C) 2005, 2007, 2009-2014, 2019-2022 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 . */ /** \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 ) 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 #include #include #include #include #include #include #define N_(String) String #if ENABLE_NLS # include # 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; } /** @} */