disks.c revision 8407
1/*
2 * The new sysinstall program.
3 *
4 * This is probably the last program in the `sysinstall' line - the next
5 * generation being essentially a complete rewrite.
6 *
7 * $Id: disks.c,v 1.13 1995/05/08 18:41:37 jkh Exp $
8 *
9 * Copyright (c) 1995
10 *	Jordan Hubbard.  All rights reserved.
11 *
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions
14 * are met:
15 * 1. Redistributions of source code must retain the above copyright
16 *    notice, this list of conditions and the following disclaimer,
17 *    verbatim and that no modifications are made prior to this
18 *    point in the file.
19 * 2. Redistributions in binary form must reproduce the above copyright
20 *    notice, this list of conditions and the following disclaimer in the
21 *    documentation and/or other materials provided with the distribution.
22 * 3. All advertising materials mentioning features or use of this software
23 *    must display the following acknowledgement:
24 *	This product includes software developed by Jordan Hubbard
25 *	for the FreeBSD Project.
26 * 4. The name of Jordan Hubbard or the FreeBSD project may not be used to
27 *    endorse or promote products derived from this software without specific
28 *    prior written permission.
29 *
30 * THIS SOFTWARE IS PROVIDED BY JORDAN HUBBARD ``AS IS'' AND
31 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
32 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33 * ARE DISCLAIMED.  IN NO EVENT SHALL JORDAN HUBBARD OR HIS PETS BE LIABLE
34 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
35 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
36 * OR SERVICES; LOSS OF USE, DATA, LIFE OR PROFITS; OR BUSINESS INTERRUPTION)
37 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
38 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
39 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
40 * SUCH DAMAGE.
41 *
42 */
43
44#include "sysinstall.h"
45#include <ctype.h>
46#include <sys/disklabel.h>
47
48/*
49 * I make some pretty gross assumptions about having a max of 50 chunks
50 * total - 8 slices and 42 partitions.  I can't easily display many more
51 * than that on the screen at once!
52 *
53 * For 2.1 I'll revisit this and try to make it more dynamic, but since
54 * this will catch 99.99% of all possible cases, I'm not too worried.
55 */
56
57#define MAX_CHUNKS	50
58
59/* Where to start printing the freebsd slices */
60#define CHUNK_SLICE_START_ROW		2
61#define CHUNK_PART_START_ROW		10
62
63/* The smallest filesystem we're willing to create */
64#define FS_MIN_SIZE			2048
65
66#define MSG_NOT_APPLICABLE		"That option is not applicable here"
67
68static struct {
69    struct disk *d;
70    struct chunk *c;
71    PartType type;
72} fbsd_chunk_info[MAX_CHUNKS + 1];
73static int current_chunk;
74
75
76/* If the given disk has a root partition on it, return TRUE */
77static Boolean
78contains_root_partition(struct disk *d)
79{
80    struct chunk *c1;
81
82    if (!d->chunks)
83	msgFatal("Disk %s has no chunks!", d->name);
84    c1 = d->chunks->part;
85    while (c1) {
86	if (c1->type == freebsd) {
87	    struct chunk *c2 = c1->part;
88
89	    while (c2) {
90		if (c2->flags & CHUNK_IS_ROOT)
91		    return TRUE;
92		c2 = c2->next;
93	    }
94	}
95	c1 = c1->next;
96    }
97    return FALSE;
98}
99
100/* Locate a chunk by position on a specific disk somewhere */
101static struct chunk *
102find_chunk_by_loc(struct disk *d, u_long offset, u_long size)
103{
104    struct chunk *c1, *c2;
105
106    if (!d->chunks)
107	msgFatal("No chunk list for %s!", d->name);
108
109    for (c1 = d->chunks->part; c1; c1 = c1->next) {
110	if (c1->type == freebsd) {
111	    if (c1->offset == offset && c1->size == size)
112		return c1;
113	    for (c2 = c1->part; c2; c2 = c2->next) {
114		if (c2->type == part && c2->offset == offset &&
115		    c2->size == size)
116		    return c2;
117	    }
118	}
119    }
120    return NULL;
121}
122
123static Boolean
124check_conflict(char *name)
125{
126    int i;
127
128    for (i = 0; fbsd_chunk_info[i].d; i++)
129	if (fbsd_chunk_info[i].type == PART_FILESYSTEM &&
130	    fbsd_chunk_info[i].c->private &&
131	    !strcmp(((PartInfo *)fbsd_chunk_info[i].c->private)->mountpoint,
132		    name))
133	    return TRUE;
134    return FALSE;
135}
136
137static int
138space_free(struct chunk *c)
139{
140    struct chunk *c1 = c->part;
141    int sz = c->size;
142
143    while (c1) {
144	if (c1->type != unused)
145	    sz -= c1->size;
146	c1 = c1->next;
147    }
148    if (sz < 0)
149	msgFatal("Partitions are larger than actual chunk??");
150    return sz;
151}
152
153static void
154record_fbsd_chunks()
155{
156    int i, j, p;
157    struct chunk *c1, *c2;
158
159    j = p = 0;
160    for (i = 0; Disks[i]; i++) {
161	if (!Disks[i]->chunks)
162	    msgFatal("No chunk list found for %s!", Disks[i]->name);
163
164	/* Put the freebsd chunks first */
165	for (c1 = Disks[i]->chunks->part; c1; c1 = c1->next) {
166	    if (c1->type == freebsd) {
167		fbsd_chunk_info[j].type = PART_SLICE;
168		fbsd_chunk_info[j].d = Disks[i];
169		fbsd_chunk_info[j].c = c1;
170		++j;
171	    }
172	}
173    }
174    for (i = 0; Disks[i]; i++) {
175	/* Then buzz through and pick up the partitions */
176	for (c1 = Disks[i]->chunks->part; c1; c1 = c1->next) {
177	    if (c1->type == freebsd) {
178		for (c2 = c1->part; c2; c2 = c2->next) {
179		    if (c2->type == part) {
180			if (c2->subtype == FS_SWAP)
181			    fbsd_chunk_info[j].type = PART_SWAP;
182			else
183			    fbsd_chunk_info[j].type = PART_FILESYSTEM;
184			fbsd_chunk_info[j].d = Disks[i];
185			fbsd_chunk_info[j].c = c2;
186			++j;
187		    }
188		}
189	    }
190	}
191    }
192    fbsd_chunk_info[j].d = NULL;
193    fbsd_chunk_info[j].c = NULL;
194    if (current_chunk >= j)
195	current_chunk = j  ? j - 1 : 0;
196}
197
198static PartInfo *
199new_part(char *mpoint, Boolean newfs)
200{
201    PartInfo *ret;
202
203    ret = (PartInfo *)safe_malloc(sizeof(PartInfo));
204    strncpy(ret->mountpoint, mpoint, FILENAME_MAX);
205    strcpy(ret->newfs_cmd, "newfs");
206    ret->newfs = newfs;
207    return ret;
208}
209
210PartInfo *
211get_mountpoint(struct chunk *c)
212{
213    char *val;
214    PartInfo *tmp;
215
216    val = msgGetInput(c && c->private ?
217		      ((PartInfo *)c->private)->mountpoint : NULL,
218		      "Please specify a mount point for the partition");
219    if (val) {
220	if (check_conflict(val)) {
221	    msgConfirm("You already have a mount point for %s assigned!", val);
222	    return NULL;
223	}
224	else if (*val != '/') {
225	    msgConfirm("Mount point must start with a / character");
226	    return NULL;
227	}
228	else if (!strcmp(val, "/")) {
229	    if (c && c->flags & CHUNK_PAST_1024) {
230msgConfirm("This region cannot be used for your root partition as\nit is past the 1024'th cylinder mark and the system would not be\nable to boot from it.  Please pick another location for your\nroot partition and try again!");
231		return NULL;
232	    }
233	    else if (c)
234		c->flags |= CHUNK_IS_ROOT;
235	}
236	else if (c)
237	    c->flags &= ~CHUNK_IS_ROOT;
238	safe_free(c ? c->private : NULL);
239	tmp = new_part(val, TRUE);
240	if (c) {
241	    c->private = tmp;
242	    c->private_free = safe_free;
243	}
244	return tmp;
245    }
246    return NULL;
247}
248
249static PartType
250get_partition_type(void)
251{
252    char selection[20];
253    static unsigned char *fs_types[] = {
254	"FS",
255	"A file system",
256	"Swap",
257	"A swap partition.",
258    };
259
260    if (!dialog_menu("Please choose a partition type",
261		    "If you want to use this partition for swap space, select Swap.\nIf you want to put a filesystem on it, choose FS.", -1, -1, 2, 2, fs_types, selection, NULL, NULL)) {
262	if (!strcmp(selection, "FS"))
263	    return PART_FILESYSTEM;
264	else if (!strcmp(selection, "Swap"))
265	    return PART_SWAP;
266    }
267    return PART_NONE;
268}
269
270static void
271getNewfsCmd(PartInfo *p)
272{
273    char *val;
274
275    val = msgGetInput(p->newfs_cmd,
276		      "Please enter the newfs command and options you'd like to use in\ncreating this file system.");
277    if (val)
278	strncpy(p->newfs_cmd, val, NEWFS_CMD_MAX);
279}
280
281
282#define MAX_MOUNT_NAME	12
283
284#define PART_PART_COL	0
285#define PART_MOUNT_COL	8
286#define PART_SIZE_COL	(PART_MOUNT_COL + MAX_MOUNT_NAME + 3)
287#define PART_NEWFS_COL	(PART_SIZE_COL + 7)
288#define PART_OFF	38
289
290/* How many mounted partitions to display in column before going to next */
291#define CHUNK_COLUMN_MAX	6
292
293static void
294print_fbsd_chunks(void)
295{
296    int i, j, srow, prow, pcol;
297    int sz;
298
299    attrset(A_REVERSE);
300    mvaddstr(0, 25, "FreeBSD Partition Editor");
301    attrset(A_NORMAL);
302
303    for (i = 0; i < 2; i++) {
304	attrset(A_UNDERLINE);
305	mvaddstr(CHUNK_PART_START_ROW - 1, PART_PART_COL + (i * PART_OFF),
306		 "Part");
307	attrset(A_NORMAL);
308
309	attrset(A_UNDERLINE);
310	mvaddstr(CHUNK_PART_START_ROW - 1, PART_MOUNT_COL + (i * PART_OFF),
311		 "Mount");
312	attrset(A_NORMAL);
313
314	attrset(A_UNDERLINE);
315	mvaddstr(CHUNK_PART_START_ROW - 1, PART_SIZE_COL + (i * PART_OFF) + 2,
316		 "Size");
317	attrset(A_NORMAL);
318
319	attrset(A_UNDERLINE);
320	mvaddstr(CHUNK_PART_START_ROW - 1, PART_NEWFS_COL + (i * PART_OFF),
321		 "Newfs");
322	attrset(A_NORMAL);
323    }
324
325    srow = CHUNK_SLICE_START_ROW;
326    prow = CHUNK_PART_START_ROW;
327    pcol = 0;
328
329    for (i = 0; fbsd_chunk_info[i].d; i++) {
330	if (i == current_chunk)
331	    attrset(A_REVERSE);
332	/* Is it a slice entry displayed at the top? */
333	if (fbsd_chunk_info[i].type == PART_SLICE) {
334	    sz = space_free(fbsd_chunk_info[i].c);
335	    mvprintw(srow++, 0,
336		     "Disk: %s\tPartition name: %s\tFree: %d blocks (%dMB)",
337		     fbsd_chunk_info[i].d->name,
338		     fbsd_chunk_info[i].c->name, sz, (sz / 2048));
339	}
340	/* Otherwise it's a swap or filesystem entry, at the bottom */
341	else {
342	    char onestr[PART_OFF], num[10], *mountpoint, *newfs;
343
344	    memset(onestr, ' ', PART_OFF - 1);
345	    onestr[PART_OFF - 1] = '\0';
346	    /* Go for two columns */
347	    if (prow == (CHUNK_PART_START_ROW + CHUNK_COLUMN_MAX)) {
348		pcol = PART_OFF;
349		prow = CHUNK_PART_START_ROW;
350	    }
351	    memcpy(onestr + PART_PART_COL, fbsd_chunk_info[i].c->name,
352		   strlen(fbsd_chunk_info[i].c->name));
353	    if (fbsd_chunk_info[i].type == PART_FILESYSTEM) {
354		if (fbsd_chunk_info[i].c->private) {
355		    mountpoint = ((PartInfo *)fbsd_chunk_info[i].c->private)->mountpoint;
356		    newfs = ((PartInfo *)fbsd_chunk_info[i].c->private)->newfs ? "Y" : "N";
357		}
358		else {
359		    fbsd_chunk_info[i].c->private = new_part("", FALSE);
360		    fbsd_chunk_info[i].c->private_free = safe_free;
361		    mountpoint = " ";
362		    newfs = "N";
363		}
364	    }
365	    else {
366		mountpoint = "swap";
367		newfs = " ";
368	    }
369	    for (j = 0; j < MAX_MOUNT_NAME && mountpoint[j]; j++)
370		onestr[PART_MOUNT_COL + j] = mountpoint[j];
371	    sprintf(num, "%4ldMB", fbsd_chunk_info[i].c->size ?
372		    fbsd_chunk_info[i].c->size / 2048 : 0);
373	    memcpy(onestr + PART_SIZE_COL, num, strlen(num));
374	    memcpy(onestr + PART_NEWFS_COL, newfs, strlen(newfs));
375	    mvaddstr(prow, pcol, onestr);
376	    ++prow;
377	}
378	if (i == current_chunk)
379	    attrset(A_NORMAL);
380    }
381}
382
383static void
384print_command_summary()
385{
386    mvprintw(17, 0,
387	     "The following commands are valid here (upper or lower case):");
388    mvprintw(19, 0, "C = Create Partition   D = Delete Partition   M = Mount Partition");
389    mvprintw(20, 0, "N = Newfs Options      T = Toggle Newfs       ESC = Finish Partitioning");
390    mvprintw(21, 0, "The default target will be displayed in ");
391
392    attrset(A_REVERSE);
393    addstr("reverse video");
394    attrset(A_NORMAL);
395    mvprintw(22, 0, "Use F1 or ? to get more help");
396    move(0, 0);
397}
398
399void
400partition_disks(void)
401{
402    int sz, key = 0;
403    Boolean partitioning;
404    char *msg = NULL;
405    PartInfo *p;
406    PartType type;
407
408    dialog_clear();
409    partitioning = TRUE;
410    keypad(stdscr, TRUE);
411    record_fbsd_chunks();
412
413    while (partitioning) {
414	clear();
415	print_fbsd_chunks();
416	print_command_summary();
417	if (msg) {
418	    attrset(A_REVERSE); mvprintw(23, 0, msg); attrset(A_NORMAL);
419	    beep();
420	    msg = NULL;
421	}
422	refresh();
423	key = toupper(getch());
424	switch (key) {
425
426	case KEY_UP:
427	case '-':
428	    if (current_chunk != 0)
429		--current_chunk;
430	    break;
431
432	case KEY_DOWN:
433	case '+':
434	case '\r':
435	case '\n':
436	    if (fbsd_chunk_info[current_chunk + 1].d)
437		++current_chunk;
438	    break;
439
440	case KEY_HOME:
441	    current_chunk = 0;
442	    break;
443
444	case KEY_END:
445	    while (fbsd_chunk_info[current_chunk + 1].d)
446		++current_chunk;
447	    break;
448
449	case KEY_F(1):
450	case '?':
451	    systemDisplayFile("partitioning.hlp");
452	    break;
453
454	case 'C':
455	    if (fbsd_chunk_info[current_chunk].type != PART_SLICE) {
456		msg = "You can only do this in a master partition (see top of screen)";
457		break;
458	    }
459	    sz = space_free(fbsd_chunk_info[current_chunk].c);
460	    if (sz <= FS_MIN_SIZE)
461		msg = "Not enough space to create additional FreeBSD partition";
462	    else {
463		char *val, *cp, tmp[20];
464		int size;
465
466		snprintf(tmp, 20, "%d", sz);
467		val = msgGetInput(tmp, "Please specify the size for new FreeBSD partition in blocks, or append\na trailing `M' for megabytes (e.g. 20M).");
468		if (val && (size = strtol(val, &cp, 0)) > 0) {
469		    struct chunk *tmp;
470		    u_long flags = 0;
471
472		    if (*cp && toupper(*cp) == 'M')
473			size *= 2048;
474
475		    type = get_partition_type();
476		    if (type == PART_NONE)
477			break;
478		    else if (type == PART_FILESYSTEM) {
479			if ((p = get_mountpoint(NULL)) == NULL)
480			    break;
481			else if (!strcmp(p->mountpoint, "/"))
482			    flags |= CHUNK_IS_ROOT;
483			else
484			    flags &= ~CHUNK_IS_ROOT;
485		    }
486		    else
487			p = NULL;
488		    Create_Chunk(fbsd_chunk_info[current_chunk].d,
489				 fbsd_chunk_info[current_chunk].c->offset +
490				 sz - size,
491				 size,
492				 part,
493				 (type == PART_SWAP) ? FS_SWAP : FS_BSDFFS,
494				 flags);
495		    tmp = find_chunk_by_loc(fbsd_chunk_info[current_chunk].d,
496					    fbsd_chunk_info[current_chunk].c->offset + sz - size, size);
497		    if (!tmp)
498			msgConfirm("Unable to create the partition.  Too big?");
499		    else {
500			tmp->private = p;
501			tmp->private_free = safe_free;
502			record_fbsd_chunks();
503		    }
504		}
505	    }
506	    break;
507
508	case 'D':	/* delete */
509	    if (fbsd_chunk_info[current_chunk].type == PART_SLICE) {
510		msg = MSG_NOT_APPLICABLE;
511		break;
512	    }
513	    Delete_Chunk(fbsd_chunk_info[current_chunk].d,
514			 fbsd_chunk_info[current_chunk].c);
515	    record_fbsd_chunks();
516	    break;
517
518	case 'M':	/* mount */
519	    switch(fbsd_chunk_info[current_chunk].type) {
520	    case PART_SLICE:
521		msg = MSG_NOT_APPLICABLE;
522		break;
523
524	    case PART_SWAP:
525		msg = "You don't need to specify a mountpoint for a swap partition.";
526		break;
527
528	    case PART_FILESYSTEM:
529		p = get_mountpoint(fbsd_chunk_info[current_chunk].c);
530		if (p) {
531		    p->newfs = FALSE;
532		    record_fbsd_chunks();
533		}
534		break;
535
536	    default:
537		msgFatal("Bogus partition under cursor???");
538		break;
539	    }
540	    break;
541
542	case 'N':	/* Set newfs options */
543	    if (fbsd_chunk_info[current_chunk].c->private &&
544		((PartInfo *)fbsd_chunk_info[current_chunk].c->private)->newfs)
545		getNewfsCmd(fbsd_chunk_info[current_chunk].c->private);
546	    else
547		msg = MSG_NOT_APPLICABLE;
548	    break;
549
550	case 'T':	/* Toggle newfs state */
551	    if (fbsd_chunk_info[current_chunk].c->private)
552		((PartInfo *)fbsd_chunk_info[current_chunk].c->private)->newfs = !((PartInfo *)fbsd_chunk_info[current_chunk].c->private)->newfs;
553	    else
554		msg = MSG_NOT_APPLICABLE;
555	    break;
556
557	case 'W':
558	    if (!msgYesNo("Are you sure you want to go into Wizard mode?\n\nThis is an entirely undocumented feature which you are not\nexpected to understand!")) {
559		int i;
560
561		clear();
562		dialog_clear();
563		end_dialog();
564		DialogActive = FALSE;
565		for (i = 0; Disks[i]; i++)
566		    slice_wizard(Disks[i]);
567		clear();
568		dialog_clear();
569		DialogActive = TRUE;
570		record_fbsd_chunks();
571	    }
572	    else
573		msg = "A most prudent choice!";
574	    break;
575
576	case 27:	/* ESC */
577	    partitioning = FALSE;
578	    break;
579
580	default:
581	    beep();
582	    msg = "Type F1 or ? for help";
583	    break;
584	}
585    }
586}
587
588int
589write_disks(void)
590{
591    int i;
592    extern u_char boot1[], boot2[];
593    extern u_char mbr[], bteasy17[];
594
595    dialog_clear();
596    for (i = 0; Disks[i]; i++) {
597	if (contains_root_partition(Disks[i]))
598	    Set_Boot_Blocks(Disks[i], boot1, boot2);
599	dialog_clear();
600	if (i == 0 && !msgYesNo("Would you like to install a boot manager?\n\nThis will allow you to easily select between other operating systems\non the first disk, or boot from a disk other than the first."))
601	    Set_Boot_Mgr(Disks[i], bteasy17);
602	else {
603	    dialog_clear();
604	    if (i == 0 && !msgYesNo("Would you like to remove an existing boot manager?"))
605		Set_Boot_Mgr(Disks[i], mbr);
606	}
607	dialog_clear();
608	if (!msgYesNo("Last Chance!  Are you sure you want to write out\nall these changes to disk?")) {
609	    Write_Disk(Disks[i]);
610	    return 0;
611	}
612    }
613    return 1;
614}
615