xref: /onnv-gate/usr/src/lib/libparted/common/libparted/unit.c (revision 9663:ace9a2ac3683)
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