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
ped_unit_set_default(PedUnit unit)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
ped_unit_get_default()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
ped_unit_get_size(const PedDevice * dev,PedUnit unit)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*
ped_unit_get_name(PedUnit unit)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
ped_unit_get_by_name(const char * unit_name)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*
ped_strdup(const char * str)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*
ped_unit_format_custom_byte(const PedDevice * dev,PedSector byte,PedUnit unit)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*
ped_unit_format_byte(const PedDevice * dev,PedSector byte)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*
ped_unit_format_custom(const PedDevice * dev,PedSector sector,PedUnit unit)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*
ped_unit_format(const PedDevice * dev,PedSector sector)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
ped_unit_parse(const char * str,const PedDevice * dev,PedSector * sector,PedGeometry ** range)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
strip_string(char * str)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*
find_suffix(const char * str)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
remove_punct(char * str)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
is_chs(const char * str)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
parse_chs(const char * str,const PedDevice * dev,PedSector * sector,PedGeometry ** range)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
clip(const PedDevice * dev,PedSector sector)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*
geometry_from_centre_radius(const PedDevice * dev,PedSector sector,PedSector radius)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
parse_unit_suffix(const char * suffix,PedUnit suggested_unit)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
ped_unit_parse_custom(const char * str,const PedDevice * dev,PedUnit unit,PedSector * sector,PedGeometry ** range)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 /** @} */
566