1 /*
   2     libparted - a library for manipulating disk partitions
   3     Copyright (C) 2005, 2007 Free Software Foundation, Inc.
   4 
   5     This program is free software; you can redistribute it and/or modify
   6     it under the terms of the GNU General Public License as published by
   7     the Free Software Foundation; either version 3 of the License, or
   8     (at your option) any later version.
   9 
  10     This program is distributed in the hope that it will be useful,
  11     but WITHOUT ANY WARRANTY; without even the implied warranty of
  12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13     GNU General Public License for more details.
  14 
  15     You should have received a copy of the GNU General Public License
  16     along with this program.  If not, see <http://www.gnu.org/licenses/>.
  17 */
  18 
  19 /** \file unit.c */
  20 
  21 /**
  22  * \addtogroup PedUnit
  23  * 
  24  * \brief The PedUnit module provides a standard mechanism for describing
  25  * and parsing locations within devices in human-friendly plain text. 
  26  *
  27  * Internally, libparted uses PedSector (which is typedef'ed to be long long 
  28  * in <parted/device.h>) to describe device locations such as the start and 
  29  * end of partitions.  However, sector numbers are often long and unintuitive.  
  30  * For example, my extended partition starts at sector 208845.  PedUnit allows 
  31  * this location to be represented in more intutitive ways, including "106Mb", 
  32  * "0Gb" and "0%", as well as "208845s".  PedUnit aims to provide facilities 
  33  * to provide a consistent system for describing device locations all
  34  * throughout libparted.
  35  * 
  36  * PedUnit provides two basic services: converting a PedSector into a text
  37  * representation, and parsing a text representation into a PedSector.
  38  * PedUnit currently supports these units:
  39  * 
  40  *      sectors, bytes, kilobytes, megabytes, gigabytes, terabytes, compact,
  41  *      cylinder and percent.
  42  *      
  43  * PedUnit has a global variable that contains the default unit for all
  44  * conversions.
  45  *
  46  * @{
  47  */
  48 
  49 
  50 
  51 
  52 #include <config.h>
  53 #include <parted/parted.h>
  54 #include <parted/debug.h>
  55 
  56 #include <ctype.h>
  57 #include <stdio.h>
  58 #include <float.h>
  59 
  60 #define N_(String) String
  61 #if ENABLE_NLS
  62 #  include <libintl.h>
  63 #  define _(String) dgettext (PACKAGE, String)
  64 #else
  65 #  define _(String) (String)
  66 #endif /* ENABLE_NLS */
  67 
  68 
  69 static PedUnit default_unit = PED_UNIT_COMPACT;
  70 static const char* unit_names[] = {
  71         "s",
  72         "B",
  73         "kB",
  74         "MB",
  75         "GB",
  76         "TB",
  77         "compact",
  78         "cyl",
  79         "chs",
  80         "%",
  81         "kiB",
  82         "MiB",
  83         "GiB",
  84         "TiB"
  85 };
  86 
  87 
  88 /**
  89  * \brief Set the default \p unit used by subsequent calls to the PedUnit API.
  90  *
  91  * In particular, this affects how locations inside error messages
  92  * (exceptions) are displayed.
  93  */
  94 void
  95 ped_unit_set_default (PedUnit unit)
  96 {
  97         default_unit = unit;
  98 }
  99 
 100 
 101 /**
 102  * \brief Get the current default unit.
 103  */
 104 PedUnit
 105 ped_unit_get_default ()
 106 {
 107         return default_unit;
 108 }
 109 
 110 /**
 111  * Get the byte size of a given \p unit.
 112  */
 113 long long
 114 ped_unit_get_size (const PedDevice* dev, PedUnit unit)
 115 {
 116         PedSector cyl_size = dev->bios_geom.heads * dev->bios_geom.sectors;
 117 
 118         switch (unit) {
 119                 case PED_UNIT_SECTOR:   return dev->sector_size;
 120                 case PED_UNIT_BYTE:     return 1;
 121                 case PED_UNIT_KILOBYTE: return PED_KILOBYTE_SIZE;
 122                 case PED_UNIT_MEGABYTE: return PED_MEGABYTE_SIZE;
 123                 case PED_UNIT_GIGABYTE: return PED_GIGABYTE_SIZE;
 124                 case PED_UNIT_TERABYTE: return PED_TERABYTE_SIZE;
 125                 case PED_UNIT_KIBIBYTE: return PED_KIBIBYTE_SIZE;
 126                 case PED_UNIT_MEBIBYTE: return PED_MEBIBYTE_SIZE;
 127                 case PED_UNIT_GIBIBYTE: return PED_GIBIBYTE_SIZE;
 128                 case PED_UNIT_TEBIBYTE: return PED_TEBIBYTE_SIZE;
 129                 case PED_UNIT_CYLINDER: return cyl_size * dev->sector_size;
 130                 case PED_UNIT_CHS:      return dev->sector_size;
 131 
 132                 case PED_UNIT_PERCENT:
 133                         return dev->length * dev->sector_size / 100;
 134 
 135                 case PED_UNIT_COMPACT:
 136                         ped_exception_throw (
 137                                 PED_EXCEPTION_ERROR, PED_EXCEPTION_CANCEL,
 138                                 _("Cannot get unit size for special unit "
 139                                   "'COMPACT'."));
 140                         return 0;
 141         }
 142 
 143         /* never reached */
 144         PED_ASSERT(0, return 0);
 145         return 0;
 146 }
 147 
 148 /**
 149  * Get a textual (non-internationalized) representation of a \p unit.
 150  * 
 151  * For example, the textual representation of PED_UNIT_SECTOR is "s".
 152  */
 153 const char*
 154 ped_unit_get_name (PedUnit unit)
 155 {
 156         return unit_names[unit];
 157 }
 158 
 159 /**
 160  * Get a unit based on its textual representation: \p unit_name.
 161  *
 162  * For example, ped_unit_get_by_name("Mb") returns PED_UNIT_MEGABYTE.
 163  */
 164 PedUnit
 165 ped_unit_get_by_name (const char* unit_name)
 166 {
 167         PedUnit unit;
 168         for (unit = PED_UNIT_FIRST; unit <= PED_UNIT_LAST; unit++) {
 169                 if (!strcasecmp (unit_names[unit], unit_name))
 170                         return unit;
 171         }
 172         return -1;
 173 }
 174 
 175 static char*
 176 ped_strdup (const char *str)
 177 {
 178         char *result;
 179         result = ped_malloc (strlen (str) + 1);
 180         if (!result)
 181                 return NULL;
 182         strcpy (result, str);
 183         return result;
 184 }
 185 
 186 /**
 187  * \brief Get a string that describes the location of the \p byte on
 188  * device \p dev.
 189  * 
 190  * The string is described with the desired \p unit.
 191  * The returned string must be freed with ped_free().
 192  */
 193 char*
 194 ped_unit_format_custom_byte (const PedDevice* dev, PedSector byte, PedUnit unit)
 195 {
 196         char buf[100];
 197         PedSector sector = byte / dev->sector_size;
 198         double d, w;
 199         int p;
 200 
 201         PED_ASSERT (dev != NULL, return NULL);
 202         
 203         /* CHS has a special comma-separated format. */
 204         if (unit == PED_UNIT_CHS) {
 205                 const PedCHSGeometry *chs = &dev->bios_geom;
 206                 snprintf (buf, 100, "%lld,%lld,%lld",
 207                           sector / chs->sectors / chs->heads,
 208                           (sector / chs->sectors) % chs->heads,
 209                           sector % chs->sectors);
 210                 return ped_strdup (buf);
 211         }
 212 
 213         /* Cylinders, sectors and bytes should be rounded down... */
 214         if (unit == PED_UNIT_CYLINDER
 215             || unit == PED_UNIT_SECTOR
 216             || unit == PED_UNIT_BYTE) {
 217                 snprintf (buf, 100, "%lld%s",
 218                           byte / ped_unit_get_size (dev, unit),
 219                           ped_unit_get_name (unit));
 220                 return ped_strdup (buf);
 221         }
 222         
 223         if (unit == PED_UNIT_COMPACT) {
 224                 if (byte >= 10LL * PED_TERABYTE_SIZE)
 225                         unit = PED_UNIT_TERABYTE;
 226                 else if (byte >= 10LL * PED_GIGABYTE_SIZE)
 227                         unit = PED_UNIT_GIGABYTE;
 228                 else if (byte >= 10LL * PED_MEGABYTE_SIZE)
 229                         unit = PED_UNIT_MEGABYTE;
 230                 else if (byte >= 10LL * PED_KILOBYTE_SIZE)
 231                         unit = PED_UNIT_KILOBYTE;
 232                 else
 233                         unit = PED_UNIT_BYTE;
 234         }
 235 
 236         /* IEEE754 says that 100.5 has to be rounded to 100 (by printf) */
 237         /* but 101.5 has to be rounded to 102... so we multiply by 1+E. */
 238         /* This just divide by 2 the natural IEEE754 extended precision */
 239         /* and won't cause any trouble before 1000 TB */
 240         d = ((double)byte / (double)ped_unit_get_size (dev, unit))
 241             * (1. + DBL_EPSILON);
 242         w = d + ( (d < 10. ) ? 0.005 :
 243                   (d < 100.) ? 0.05  :
 244                                0.5  );
 245         p = (w < 10. ) ? 2 :
 246             (w < 100.) ? 1 :
 247                          0 ;
 248 
 249 #ifdef __BEOS__
 250         snprintf (buf, 100, "%.*f%s", p, d, ped_unit_get_name(unit));
 251 #else
 252         snprintf (buf, 100, "%1$.*2$f%3$s", d, p, ped_unit_get_name (unit));
 253 #endif
 254 
 255         return ped_strdup (buf);
 256 }
 257 
 258 /**
 259  * \brief Get a string that describes the location of the \p byte on
 260  * device \p dev.
 261  * 
 262  * The string is described with the default unit, which is set
 263  * by ped_unit_set_default().
 264  * The returned string must be freed with ped_free().
 265  */
 266 char*
 267 ped_unit_format_byte (const PedDevice* dev, PedSector byte)
 268 {
 269         PED_ASSERT (dev != NULL, return NULL);
 270         return ped_unit_format_custom_byte (dev, byte, default_unit);
 271 }
 272 
 273 /**
 274  * \brief Get a string that describes the location \p sector on device \p dev.
 275  * 
 276  * The string is described with the desired \p unit.
 277  * The returned string must be freed with ped_free().
 278  */
 279 char*
 280 ped_unit_format_custom (const PedDevice* dev, PedSector sector, PedUnit unit)
 281 {
 282         PED_ASSERT (dev != NULL, return NULL);
 283         return ped_unit_format_custom_byte(dev, sector*dev->sector_size, unit);
 284 }
 285 
 286 /**
 287  * \brief Get a string that describes the location \p sector on device \p dev.
 288  * 
 289  * The string is described with the default unit, which is set
 290  * by ped_unit_set_default().
 291  * The returned string must be freed with ped_free().
 292  */
 293 char*
 294 ped_unit_format (const PedDevice* dev, PedSector sector)
 295 {
 296         PED_ASSERT (dev != NULL, return NULL);
 297         return ped_unit_format_custom_byte (dev, sector * dev->sector_size,
 298                                             default_unit);
 299 }
 300 
 301 /**
 302  * If \p str contains a valid description of a location on \p dev, 
 303  * then \p *sector is modified to describe the location and a geometry 
 304  * is created in \p *range describing a 2 units large area centered on 
 305  * \p *sector.  If the \p range as described here would be partially outside 
 306  * the device \p dev, the geometry returned is the intersection between the 
 307  * former and the whole device geometry.  If no units are specified, then the 
 308  * default unit is assumed.  
 309  *
 310  * \return \c 1 if \p str is a valid location description, \c 0 otherwise
 311  */
 312 int
 313 ped_unit_parse (const char* str, const PedDevice* dev, PedSector *sector,
 314                 PedGeometry** range)
 315 {
 316         return ped_unit_parse_custom (str, dev, default_unit, sector, range);
 317 }
 318 
 319 /* Inefficiently removes all spaces from a string, in-place. */
 320 static void
 321 strip_string (char* str)
 322 {
 323         int i;
 324 
 325         for (i = 0; str[i] != 0; i++) {
 326                 if (isspace (str[i])) {
 327                         int j;
 328                         for (j = i + 1; str[j] != 0; j++)
 329                                 str[j - 1] = str[j];
 330                 }
 331         }
 332 }
 333 
 334 
 335 /* Find non-number suffix.  Eg: find_suffix("32Mb") returns a pointer to
 336  * "Mb". */
 337 static char*
 338 find_suffix (const char* str)
 339 {
 340         while (str[0] != 0 && (isdigit (str[0]) || strchr(",.-", str[0])))
 341                 str++;
 342         return (char *) str;
 343 }
 344 
 345 static void
 346 remove_punct (char* str)
 347 {
 348         int i = 0;
 349 
 350         for (i = 0; str[i]; i++) {
 351                 if (ispunct (str[i]))
 352                         str[i] = ' ';
 353         }
 354 }
 355 
 356 static int
 357 is_chs (const char* str)
 358 {
 359         int punct_count = 0;
 360         int i = 0;
 361 
 362         for (i = 0; str[i]; i++)
 363                 punct_count += ispunct (str[i]) != 0;
 364         return punct_count == 2;
 365 }
 366 
 367 static int
 368 parse_chs (const char* str, const PedDevice* dev, PedSector* sector,
 369                 PedGeometry** range)
 370 {
 371         PedSector cyl_size = dev->bios_geom.heads * dev->bios_geom.sectors;
 372         char* copy = ped_strdup (str);
 373         PedCHSGeometry chs;
 374 
 375         copy = ped_strdup (str);
 376         if (!copy)
 377                 return 0;
 378         strip_string (copy);
 379         remove_punct (copy);
 380 
 381         if (sscanf (copy, "%d %d %d",
 382                     &chs.cylinders, &chs.heads, &chs.sectors) != 3) {
 383                 ped_exception_throw (
 384                                 PED_EXCEPTION_ERROR, PED_EXCEPTION_CANCEL,
 385                                 _("\"%s\" has invalid syntax for locations."),
 386                                 copy);
 387                 goto error_free_copy;
 388         }
 389 
 390         if (chs.heads >= dev->bios_geom.heads) {
 391                 ped_exception_throw (
 392                                 PED_EXCEPTION_ERROR, PED_EXCEPTION_CANCEL,
 393                                 _("The maximum head value is %d."),
 394                                 dev->bios_geom.heads - 1);
 395                 goto error_free_copy;
 396         }
 397         if (chs.sectors >= dev->bios_geom.sectors) {
 398                 ped_exception_throw (
 399                                 PED_EXCEPTION_ERROR, PED_EXCEPTION_CANCEL,
 400                                 _("The maximum sector value is %d."),
 401                                 dev->bios_geom.sectors - 1);
 402                 goto error_free_copy;
 403         }
 404 
 405         *sector = 1LL * chs.cylinders * cyl_size
 406                 + chs.heads * dev->bios_geom.sectors
 407                 + chs.sectors;
 408 
 409         if (*sector >= dev->length) {
 410                 ped_exception_throw (
 411                                 PED_EXCEPTION_ERROR, PED_EXCEPTION_CANCEL,
 412                                 _("The location %s is outside of the "
 413                                   "device %s."),
 414                                 str, dev->path);
 415                 goto error_free_copy;
 416         }
 417         if (range)
 418                 *range = ped_geometry_new (dev, *sector, 1);
 419         ped_free (copy);
 420         return !range || *range != NULL;
 421 
 422 error_free_copy:
 423         ped_free (copy);
 424         *sector = 0;
 425         if (range)
 426                 *range = NULL;
 427         return 0;
 428 }
 429 
 430 static PedSector
 431 clip (const PedDevice* dev, PedSector sector)
 432 {
 433         if (sector < 0)
 434                 return 0;
 435         if (sector > dev->length - 1)
 436                 return dev->length - 1;
 437         return sector;
 438 }
 439 
 440 static PedGeometry*
 441 geometry_from_centre_radius (const PedDevice* dev,
 442                              PedSector sector, PedSector radius)
 443 {
 444         PedSector start = clip (dev, sector - radius);
 445         PedSector end = clip (dev, sector + radius);
 446         if (sector - end > radius || start - sector > radius)
 447                 return NULL;
 448         return ped_geometry_new (dev, start, end - start + 1);
 449 }
 450 
 451 static PedUnit
 452 parse_unit_suffix (const char* suffix, PedUnit suggested_unit)
 453 {
 454         if (strlen (suffix) > 1 && tolower (suffix[1]) == 'i') {
 455                 switch (tolower (suffix[0])) {
 456                         case 'k': return PED_UNIT_KIBIBYTE;
 457                         case 'm': return PED_UNIT_MEBIBYTE;
 458                         case 'g': return PED_UNIT_GIBIBYTE;
 459                         case 't': return PED_UNIT_TEBIBYTE;
 460                 }
 461         } else if (strlen (suffix) > 0) {
 462                 switch (tolower (suffix[0])) {
 463                         case 's': return PED_UNIT_SECTOR;
 464                         case 'b': return PED_UNIT_BYTE;
 465                         case 'k': return PED_UNIT_KILOBYTE;
 466                         case 'm': return PED_UNIT_MEGABYTE;
 467                         case 'g': return PED_UNIT_GIGABYTE; 
 468                         case 't': return PED_UNIT_TERABYTE;
 469                         case 'c': return PED_UNIT_CYLINDER; 
 470                         case '%': return PED_UNIT_PERCENT;
 471                 }
 472         }
 473 
 474         if (suggested_unit == PED_UNIT_COMPACT) {
 475                 if (default_unit == PED_UNIT_COMPACT)
 476                         return PED_UNIT_MEGABYTE;
 477                 else
 478                         return default_unit;
 479         }
 480 
 481         return suggested_unit;
 482 }
 483 
 484 /**
 485  * If \p str contains a valid description of a location on \p dev, then 
 486  * \p *sector is modified to describe the location and a geometry is created
 487  * in \p *range describing a 2 units large area centered on \p *sector.  If the
 488  * \p range as described here would be partially outside the device \p dev, the
 489  * geometry returned is the intersection between the former and the whole
 490  * device geometry.  If no units are specified, then the default unit is
 491  * assumed. 
 492  *
 493  * \throws PED_EXCEPTION_ERROR if \p str contains invalid description of a
 494  * location
 495  * \throws PED_EXCEPTION_ERROR if location described by \p str 
 496  * is outside of the device \p dev->path
 497  *
 498  * \return \c 1 if \p str is a valid location description, \c 0 otherwise.
 499  */
 500 int
 501 ped_unit_parse_custom (const char* str, const PedDevice* dev, PedUnit unit,
 502                        PedSector* sector, PedGeometry** range)
 503 {
 504         char*     copy;
 505         char*     suffix;
 506         double    num;
 507         long long unit_size;
 508         PedSector radius;
 509 
 510         if (is_chs (str))
 511                 return parse_chs (str, dev, sector, range);
 512 
 513         copy = ped_strdup (str);
 514         if (!copy)
 515                 goto error;
 516         strip_string (copy);
 517 
 518         suffix = find_suffix (copy);
 519         unit = parse_unit_suffix (suffix, unit);
 520         suffix[0] = 0;
 521 
 522         if (sscanf (copy, "%lf", &num) != 1) {
 523                 ped_exception_throw (
 524                                 PED_EXCEPTION_ERROR,
 525                                 PED_EXCEPTION_CANCEL,
 526                                 _("Invalid number."));
 527                 goto error_free_copy;
 528         }
 529 
 530         unit_size = ped_unit_get_size (dev, unit);
 531         radius = ped_div_round_up (unit_size, dev->sector_size) - 1;
 532         if (radius < 0)
 533                 radius = 0;
 534 
 535         *sector = num * unit_size / dev->sector_size;
 536         /* negative numbers count from the end */
 537         if (copy[0] == '-')
 538                 *sector += dev->length;
 539         if (range) {
 540                 *range = geometry_from_centre_radius (dev, *sector, radius);
 541                 if (!*range) {
 542                         ped_exception_throw (
 543                                 PED_EXCEPTION_ERROR, PED_EXCEPTION_CANCEL,
 544                                 _("The location %s is outside of the "
 545                                   "device %s."),
 546                                 str, dev->path);
 547                         goto error_free_copy;
 548                 }
 549         }
 550         *sector = clip (dev, *sector);
 551 
 552         ped_free (copy);
 553         return 1;
 554 
 555 error_free_copy:
 556         ped_free (copy);
 557 error:
 558         *sector = 0;
 559         if (range)
 560                 *range = NULL;
 561         return 0;
 562 }
 563 
 564 
 565 /** @} */