1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright (c) 1991, 2010, Oracle and/or its affiliates. All rights reserved.
23  */
24 
25 /*
26  * This file contains functions to implement the partition menu commands.
27  */
28 #include "global.h"
29 #include <stdlib.h>
30 #include <string.h>
31 
32 #include "partition.h"
33 #include "menu_partition.h"
34 #include "menu_command.h"
35 #include "misc.h"
36 #include "param.h"
37 
38 /* Function prototypes for ANSI C Compilers */
39 static void	nspaces(int);
40 static int	ndigits(uint64_t);
41 
42 /*
43  * This routine implements the 'a' command.  It changes the 'a' partition.
44  */
45 int
46 p_apart(void)
47 {
48 
49 	change_partition(0);
50 	return (0);
51 }
52 
53 /*
54  * This routine implements the 'b' command.  It changes the 'b' partition.
55  */
56 int
57 p_bpart(void)
58 {
59 
60 	change_partition(1);
61 	return (0);
62 }
63 
64 /*
65  * This routine implements the 'c' command.  It changes the 'c' partition.
66  */
67 int
68 p_cpart(void)
69 {
70 
71 	change_partition(2);
72 	return (0);
73 }
74 
75 /*
76  * This routine implements the 'd' command.  It changes the 'd' partition.
77  */
78 int
79 p_dpart(void)
80 {
81 
82 	change_partition(3);
83 	return (0);
84 }
85 
86 /*
87  * This routine implements the 'e' command.  It changes the 'e' partition.
88  */
89 int
90 p_epart(void)
91 {
92 
93 	change_partition(4);
94 	return (0);
95 }
96 
97 /*
98  * This routine implements the 'f' command.  It changes the 'f' partition.
99  */
100 int
101 p_fpart(void)
102 {
103 
104 	change_partition(5);
105 	return (0);
106 }
107 
108 /*
109  * This routine implements the 'g' command.  It changes the 'g' partition.
110  */
111 int
112 p_gpart(void)
113 {
114 
115 	change_partition(6);
116 	return (0);
117 }
118 
119 /*
120  * This routine implements the 'h' command.  It changes the 'h' partition.
121  */
122 int
123 p_hpart(void)
124 {
125 
126 	change_partition(7);
127 	return (0);
128 }
129 
130 /*
131  * This routine implements the 'i' command. It is valid only for EFI
132  * labeled disks. This can be used only in expert mode.
133  */
134 int
135 p_ipart(void)
136 {
137 	change_partition(8);
138 	return (0);
139 }
140 
141 #if defined(i386)
142 /*
143  * This routine implements the 'j' command.  It changes the 'j' partition.
144  */
145 int
146 p_jpart(void)
147 {
148 
149 	change_partition(9);
150 	return (0);
151 }
152 #endif	/* defined(i386) */
153 
154 int
155 p_expand(void)
156 {
157 	uint64_t delta;
158 	uint_t nparts;
159 	struct dk_gpt *efi_label = cur_parts->etoc;
160 
161 	if (cur_parts->etoc->efi_altern_lba == 1 ||
162 	    (cur_parts->etoc->efi_altern_lba >=
163 	    cur_parts->etoc->efi_last_lba)) {
164 		err_print("Warning: No expanded capacity is found.\n");
165 		return (0);
166 	}
167 
168 	delta = efi_label->efi_last_lba - efi_label->efi_altern_lba;
169 	nparts = efi_label->efi_nparts;
170 
171 	enter_critical();
172 	efi_label->efi_parts[nparts - 1].p_start += delta;
173 	efi_label->efi_last_u_lba += delta;
174 	efi_label->efi_altern_lba = cur_parts->etoc->efi_last_lba;
175 	exit_critical();
176 
177 	fmt_print("The expanded capacity is added to the unallocated space.\n");
178 	return (0);
179 }
180 
181 /*
182  * This routine implements the 'select' command.  It allows the user
183  * to make a pre-defined partition map the current map.
184  */
185 int
186 p_select(void)
187 {
188 	struct partition_info	*pptr, *parts;
189 	u_ioparam_t		ioparam;
190 	int			i, index, deflt, *defltptr = NULL;
191 	blkaddr_t		b_cylno;
192 #if defined(i386)
193 	blkaddr_t		cyl_offset;
194 #endif
195 
196 	parts = cur_dtype->dtype_plist;
197 	/*
198 	 * If there are no pre-defined maps for this disk type, it's
199 	 * an error.
200 	 */
201 	if (parts == NULL) {
202 		err_print("No defined partition tables.\n");
203 		return (-1);
204 	}
205 
206 	/*
207 	 * Loop through the pre-defined maps and list them by name.  If
208 	 * the current map is one of them, make it the default.  If any
209 	 * the maps are unnamed, label them as such.
210 	 */
211 	for (i = 0, pptr = parts; pptr != NULL; pptr = pptr->pinfo_next) {
212 		if (cur_parts == pptr) {
213 			deflt = i;
214 			defltptr = &deflt;
215 		}
216 		if (pptr->pinfo_name == NULL)
217 			fmt_print("        %d. unnamed\n", i++);
218 		else
219 			fmt_print("        %d. %s\n", i++, pptr->pinfo_name);
220 	}
221 	ioparam.io_bounds.lower = 0;
222 	ioparam.io_bounds.upper = i - 1;
223 	/*
224 	 * Ask which map should be made current.
225 	 */
226 	index = input(FIO_INT, "Specify table (enter its number)", ':',
227 	    &ioparam, defltptr, DATA_INPUT);
228 	for (i = 0, pptr = parts; i < index; i++, pptr = pptr->pinfo_next)
229 		;
230 	if (cur_label == L_TYPE_EFI) {
231 		enter_critical();
232 		cur_disk->disk_parts = cur_parts = pptr;
233 		exit_critical();
234 		fmt_print("\n");
235 		return (0);
236 	}
237 #if defined(i386)
238 	/*
239 	 * Adjust for the boot and alternate sectors partition - assuming that
240 	 * the alternate sectors partition physical location follows
241 	 * immediately the boot partition and partition sizes are
242 	 * expressed in multiple of cylinder size.
243 	 */
244 	cyl_offset = pptr->pinfo_map[I_PARTITION].dkl_cylno + 1;
245 	if (pptr->pinfo_map[J_PARTITION].dkl_nblk != 0) {
246 		cyl_offset = pptr->pinfo_map[J_PARTITION].dkl_cylno +
247 		    ((pptr->pinfo_map[J_PARTITION].dkl_nblk +
248 		    (spc() - 1)) / spc());
249 	}
250 #else	/* !defined(i386) */
251 
252 	b_cylno = 0;
253 
254 #endif	/* defined(i386) */
255 
256 	/*
257 	 * Before we blow the current map away, do some limits checking.
258 	 */
259 	for (i = 0; i < NDKMAP; i++)  {
260 
261 #if defined(i386)
262 		if (i == I_PARTITION || i == J_PARTITION || i == C_PARTITION) {
263 			b_cylno = 0;
264 		} else if (pptr->pinfo_map[i].dkl_nblk == 0) {
265 			/*
266 			 * Always accept starting cyl 0 if the size is 0 also
267 			 */
268 			b_cylno = 0;
269 		} else {
270 			b_cylno = cyl_offset;
271 		}
272 #endif		/* defined(i386) */
273 		if (pptr->pinfo_map[i].dkl_cylno < b_cylno ||
274 		    pptr->pinfo_map[i].dkl_cylno > (ncyl-1)) {
275 			err_print("partition %c: starting cylinder %d is out "
276 			    "of range\n", (PARTITION_BASE + i),
277 			    pptr->pinfo_map[i].dkl_cylno);
278 			return (0);
279 		}
280 		if (pptr->pinfo_map[i].dkl_nblk > ((ncyl -
281 		    pptr->pinfo_map[i].dkl_cylno) * spc())) {
282 			err_print(
283 			    "partition %c: specified # of blocks, %u, "
284 			    "is out of range\n",
285 			    (PARTITION_BASE+i),
286 			    pptr->pinfo_map[i].dkl_nblk);
287 			return (0);
288 		}
289 	}
290 	/*
291 	 * Lock out interrupts so the lists don't get mangled.
292 	 */
293 	enter_critical();
294 	/*
295 	 * If the old current map is unnamed, delete it.
296 	 */
297 	if (cur_parts != NULL && cur_parts != pptr &&
298 	    cur_parts->pinfo_name == NULL)
299 		delete_partition(cur_parts);
300 	/*
301 	 * Make the selected map current.
302 	 */
303 	cur_disk->disk_parts = cur_parts = pptr;
304 
305 #if defined(_SUNOS_VTOC_16)
306 	for (i = 0; i < NDKMAP; i++)  {
307 		cur_parts->vtoc.v_part[i].p_start =
308 		    (blkaddr_t)(cur_parts->pinfo_map[i].dkl_cylno *
309 		    (nhead * nsect));
310 		cur_parts->vtoc.v_part[i].p_size =
311 		    (blkaddr_t)cur_parts->pinfo_map[i].dkl_nblk;
312 	}
313 #endif	/* defined(_SUNOS_VTOC_16) */
314 
315 	exit_critical();
316 	fmt_print("\n");
317 	return (0);
318 }
319 
320 /*
321  * This routine implements the 'name' command.  It allows the user
322  * to name the current partition map.  If the map was already named,
323  * the name is changed.  Once a map is named, the values of the partitions
324  * cannot be changed.  Attempts to change them will cause another map
325  * to be created.
326  */
327 int
328 p_name(void)
329 {
330 	char	*name;
331 
332 	/*
333 	 * check if there exists a partition table for the disk.
334 	 */
335 	if (cur_parts == NULL) {
336 		err_print("Current Disk has no partition table.\n");
337 		return (-1);
338 	}
339 
340 
341 	/*
342 	 * Ask for the name.  Note that the input routine will malloc
343 	 * space for the name since we are using the OSTR input type.
344 	 */
345 	name = (char *)(uintptr_t)input(FIO_OSTR,
346 	    "Enter table name (remember quotes)",
347 	    ':', (u_ioparam_t *)NULL, (int *)NULL, DATA_INPUT);
348 	/*
349 	 * Lock out interrupts.
350 	 */
351 	enter_critical();
352 	/*
353 	 * If it was already named, destroy the old name.
354 	 */
355 	if (cur_parts->pinfo_name != NULL)
356 		destroy_data(cur_parts->pinfo_name);
357 	/*
358 	 * Set the name.
359 	 */
360 	cur_parts->pinfo_name = name;
361 	exit_critical();
362 	fmt_print("\n");
363 	return (0);
364 }
365 
366 
367 /*
368  * This routine implements the 'print' command.  It lists the values
369  * for all the partitions in the current partition map.
370  */
371 int
372 p_print(void)
373 {
374 	/*
375 	 * check if there exists a partition table for the disk.
376 	 */
377 	if (cur_parts == NULL) {
378 		err_print("Current Disk has no partition table.\n");
379 		return (-1);
380 	}
381 
382 	/*
383 	 * Print the volume name, if it appears to be set
384 	 */
385 	if (chk_volname(cur_disk)) {
386 		fmt_print("Volume:  ");
387 		print_volname(cur_disk);
388 		fmt_print("\n");
389 	}
390 	/*
391 	 * Print the name of the current map.
392 	 */
393 	if ((cur_parts->pinfo_name != NULL) && (cur_label == L_TYPE_SOLARIS)) {
394 		fmt_print("Current partition table (%s):\n",
395 		    cur_parts->pinfo_name);
396 		fmt_print("Total disk cylinders available: %d + %d "
397 		    "(reserved cylinders)\n\n", ncyl, acyl);
398 	} else if (cur_label == L_TYPE_SOLARIS) {
399 		fmt_print("Current partition table (unnamed):\n");
400 		fmt_print("Total disk cylinders available: %d + %d "
401 		    "(reserved cylinders)\n\n", ncyl, acyl);
402 	} else if (cur_label == L_TYPE_EFI) {
403 		unsigned reserved;
404 
405 		reserved = efi_reserved_sectors(cur_parts->etoc);
406 		fmt_print("Current partition table (%s):\n",
407 		    cur_parts->pinfo_name != NULL ?
408 		    cur_parts->pinfo_name : "unnamed");
409 		fmt_print("Total disk sectors available: %llu + %u "
410 		    "(reserved sectors)\n\n",
411 		    cur_parts->etoc->efi_last_u_lba - reserved -
412 		    cur_parts->etoc->efi_first_u_lba + 1, reserved);
413 	}
414 
415 
416 	/*
417 	 * Print the partition map itself
418 	 */
419 	print_map(cur_parts);
420 	return (0);
421 }
422 
423 
424 /*
425  * Print a partition map
426  */
427 void
428 print_map(struct partition_info *map)
429 {
430 	int	i;
431 	int	want_header;
432 	struct	dk_gpt *vtoc64;
433 
434 	if (cur_label == L_TYPE_EFI) {
435 		vtoc64 = map->etoc;
436 		want_header = 1;
437 		for (i = 0; i < vtoc64->efi_nparts; i++) {
438 		/*
439 		 * we want to print partitions above 7 in expert mode only
440 		 * or if the partition is reserved
441 		 */
442 			if (i >= 7 && !expert_mode &&
443 			    ((int)vtoc64->efi_parts[i].p_tag !=
444 			    V_RESERVED)) {
445 				continue;
446 			}
447 
448 			print_efi_partition(vtoc64, i, want_header);
449 			want_header = 0;
450 		}
451 		fmt_print("\n");
452 		return;
453 	}
454 	/*
455 	 * Loop through each partition, printing the header
456 	 * the first time.
457 	 */
458 	want_header = 1;
459 	for (i = 0; i < NDKMAP; i++) {
460 		if (i > 9) {
461 			break;
462 		}
463 		print_partition(map, i, want_header);
464 		want_header = 0;
465 	}
466 
467 	fmt_print("\n");
468 }
469 
470 /*
471  * Print out one line of partition information,
472  * with optional header for EFI type disks.
473  */
474 /*ARGSUSED*/
475 void
476 print_efi_partition(struct dk_gpt *map, int partnum, int want_header)
477 {
478 	int		ncyl2_digits = 0;
479 	float		scaled;
480 	char		*s;
481 	uint64_t	secsize;
482 
483 	ncyl2_digits = ndigits(map->efi_last_u_lba);
484 	if (want_header) {
485 		fmt_print("Part      ");
486 		fmt_print("Tag    Flag     ");
487 		fmt_print("First Sector");
488 		nspaces(ncyl2_digits);
489 		fmt_print("Size");
490 		nspaces(ncyl2_digits);
491 		fmt_print("Last Sector\n");
492 	}
493 
494 	fmt_print("  %d ", partnum);
495 	s = find_string(ptag_choices, (int)map->efi_parts[partnum].p_tag);
496 	if (s == (char *)NULL)
497 		s = "-";
498 	nspaces(10 - (int)strlen(s));
499 	fmt_print("%s", s);
500 
501 	s = find_string(pflag_choices, (int)map->efi_parts[partnum].p_flag);
502 	if (s == (char *)NULL)
503 		s = "-";
504 	nspaces(6 - (int)strlen(s));
505 	fmt_print("%s", s);
506 
507 	nspaces(2);
508 
509 	secsize = map->efi_parts[partnum].p_size;
510 	if (secsize == 0) {
511 		fmt_print("%16llu", map->efi_parts[partnum].p_start);
512 		nspaces(ncyl2_digits);
513 		fmt_print("  0     ");
514 	} else {
515 		fmt_print("%16llu", map->efi_parts[partnum].p_start);
516 		scaled = bn2mb(secsize);
517 		nspaces(ncyl2_digits - 5);
518 		if (scaled >= (float)1024.0 * 1024) {
519 			fmt_print("%8.2fTB", scaled/((float)1024.0 * 1024));
520 		} else if (scaled >= (float)1024.0) {
521 			fmt_print("%8.2fGB", scaled/(float)1024.0);
522 		} else {
523 			fmt_print("%8.2fMB", scaled);
524 		}
525 	}
526 	nspaces(ncyl2_digits);
527 	if ((map->efi_parts[partnum].p_start + secsize - 1) == UINT_MAX64) {
528 		fmt_print(" 0    \n");
529 	} else {
530 		fmt_print(" %llu    \n",
531 		    map->efi_parts[partnum].p_start + secsize - 1);
532 	}
533 }
534 
535 /*
536  * Print out one line of partition information,
537  * with optional header.
538  */
539 /*ARGSUSED*/
540 void
541 print_partition(struct partition_info *pinfo, int partnum, int want_header)
542 {
543 	int		i;
544 	blkaddr_t	nblks;
545 	int		cyl1;
546 	int		cyl2;
547 	float		scaled;
548 	int		maxcyl2;
549 	int		ncyl2_digits;
550 	char		*s;
551 	blkaddr_t	maxnblks = 0;
552 	blkaddr_t	len;
553 
554 	/*
555 	 * To align things nicely, we need to know the maximum
556 	 * width of the number of cylinders field.
557 	 */
558 	maxcyl2 = 0;
559 	for (i = 0; i < NDKMAP; i++) {
560 		nblks	= (uint_t)pinfo->pinfo_map[i].dkl_nblk;
561 		cyl1	= pinfo->pinfo_map[i].dkl_cylno;
562 		cyl2	= cyl1 + (nblks / spc()) - 1;
563 		if (nblks > 0) {
564 			maxcyl2 = max(cyl2, maxcyl2);
565 			maxnblks = max(nblks, maxnblks);
566 		}
567 	}
568 	/*
569 	 * Get the number of digits required
570 	 */
571 	ncyl2_digits = ndigits(maxcyl2);
572 
573 	/*
574 	 * Print the header, if necessary
575 	 */
576 	if (want_header) {
577 		fmt_print("Part      ");
578 		fmt_print("Tag    Flag     ");
579 		fmt_print("Cylinders");
580 		nspaces(ncyl2_digits);
581 		fmt_print("    Size            Blocks\n");
582 	}
583 
584 	/*
585 	 * Print the partition information
586 	 */
587 	nblks	= pinfo->pinfo_map[partnum].dkl_nblk;
588 	cyl1	= pinfo->pinfo_map[partnum].dkl_cylno;
589 	cyl2	= cyl1 + (nblks / spc()) - 1;
590 
591 	fmt_print("  %x ", partnum);
592 
593 	/*
594 	 * Print the partition tag.  If invalid, print -
595 	 */
596 	s = find_string(ptag_choices, (int)pinfo->vtoc.v_part[partnum].p_tag);
597 	if (s == (char *)NULL)
598 		s = "-";
599 	nspaces(10 - (int)strlen(s));
600 	fmt_print("%s", s);
601 
602 	/*
603 	 * Print the partition flag.  If invalid print -
604 	 */
605 	s = find_string(pflag_choices, (int)pinfo->vtoc.v_part[partnum].p_flag);
606 	if (s == NULL)
607 		s = "-";
608 	nspaces(6 - (int)strlen(s));
609 	fmt_print("%s", s);
610 
611 	nspaces(2);
612 
613 	if (nblks == 0) {
614 		fmt_print("%6d      ", cyl1);
615 		nspaces(ncyl2_digits);
616 		fmt_print("     0         ");
617 	} else {
618 		fmt_print("%6d - ", cyl1);
619 		nspaces(ncyl2_digits - ndigits(cyl2));
620 		fmt_print("%d    ", cyl2);
621 		scaled = bn2mb(nblks);
622 		if (scaled > (float)1024.0 * 1024.0) {
623 			fmt_print("%8.2fTB    ",
624 			    scaled/((float)1024.0 * 1024.0));
625 		} else if (scaled > (float)1024.0) {
626 			fmt_print("%8.2fGB    ", scaled/(float)1024.0);
627 		} else {
628 			fmt_print("%8.2fMB    ", scaled);
629 		}
630 	}
631 	fmt_print("(");
632 	pr_dblock(fmt_print, nblks);
633 	fmt_print(")");
634 
635 	nspaces(ndigits(maxnblks/spc()) - ndigits(nblks/spc()));
636 	/*
637 	 * Allocates size of the printf format string.
638 	 * ndigits(ndigits(maxblks)) gives the byte size of
639 	 * the printf width field for maxnblks.
640 	 */
641 	len = strlen(" %") + ndigits(ndigits(maxnblks)) + strlen("d\n") + 1;
642 	s = zalloc(len);
643 	(void) snprintf(s, len, "%s%u%s", " %", ndigits(maxnblks), "u\n");
644 	fmt_print(s, nblks);
645 	(void) free(s);
646 }
647 
648 
649 /*
650  * Return true if a disk has a volume name
651  */
652 int
653 chk_volname(struct disk_info *disk)
654 {
655 	return (disk->v_volume[0] != 0);
656 }
657 
658 
659 /*
660  * Print the volume name, if it appears to be set
661  */
662 void
663 print_volname(struct disk_info *disk)
664 {
665 	int	i;
666 	char	*p;
667 
668 	p = disk->v_volume;
669 	for (i = 0; i < LEN_DKL_VVOL; i++, p++) {
670 		if (*p == 0)
671 			break;
672 		fmt_print("%c", *p);
673 	}
674 }
675 
676 
677 /*
678  * Print a number of spaces
679  */
680 static void
681 nspaces(int n)
682 {
683 	while (n-- > 0)
684 		fmt_print(" ");
685 }
686 
687 /*
688  * Return the number of digits required to print a number
689  */
690 static int
691 ndigits(uint64_t n)
692 {
693 	int	i;
694 
695 	i = 0;
696 	while (n > 0) {
697 		n /= 10;
698 		i++;
699 	}
700 
701 	return (i == 0 ? 1 : i);
702 }
703