/* 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 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 #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 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 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 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 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 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); 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; } 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); free (copy); return 1; error_free_copy: free (copy); error: *sector = 0; if (range) *range = NULL; return 0; } /** @} */