| /* |
| libparted - a library for manipulating disk partitions |
| Copyright (C) 2005, 2007 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 2 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, write to the Free Software |
| Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA |
| */ |
| |
| /** \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 <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 |
| 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); |
| 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 ped_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, return 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 / (double)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 ped_free(). |
| */ |
| char* |
| ped_unit_format_byte (const PedDevice* dev, PedSector byte) |
| { |
| PED_ASSERT (dev != NULL, return 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 ped_free(). |
| */ |
| char* |
| ped_unit_format_custom (const PedDevice* dev, PedSector sector, PedUnit unit) |
| { |
| PED_ASSERT (dev != NULL, return 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 ped_free(). |
| */ |
| char* |
| ped_unit_format (const PedDevice* dev, PedSector sector) |
| { |
| PED_ASSERT (dev != NULL, return 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* |
| 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 |
| 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; |
| char* copy = ped_strdup (str); |
| PedCHSGeometry chs; |
| |
| 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); |
| ped_free (copy); |
| return !range || *range != NULL; |
| |
| error_free_copy: |
| ped_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; |
| } |
| |
| unit_size = ped_unit_get_size (dev, unit); |
| radius = ped_div_round_up (unit_size, dev->sector_size) - 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); |
| |
| ped_free (copy); |
| return 1; |
| |
| error_free_copy: |
| ped_free (copy); |
| error: |
| *sector = 0; |
| if (range) |
| *range = NULL; |
| return 0; |
| } |
| |
| |
| /** @} */ |