disks.c revision 8452
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.16 1995/05/11 06:10:48 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
76static Boolean
77check_conflict(char *name)
78{
79    int i;
80
81    for (i = 0; fbsd_chunk_info[i].d; i++)
82	if (fbsd_chunk_info[i].type == PART_FILESYSTEM &&
83	    fbsd_chunk_info[i].c->private &&
84	    !strcmp(((PartInfo *)fbsd_chunk_info[i].c->private)->mountpoint,
85		    name))
86	    return TRUE;
87    return FALSE;
88}
89
90static int
91space_free(struct chunk *c)
92{
93    struct chunk *c1 = c->part;
94    int sz = c->size;
95
96    while (c1) {
97	if (c1->type != unused)
98	    sz -= c1->size;
99	c1 = c1->next;
100    }
101    if (sz < 0)
102	msgFatal("Partitions are larger than actual chunk??");
103    return sz;
104}
105
106static void
107record_fbsd_chunks()
108{
109    int i, j, p;
110    struct chunk *c1, *c2;
111
112    j = p = 0;
113    for (i = 0; Disks[i]; i++) {
114	if (!Disks[i]->chunks)
115	    msgFatal("No chunk list found for %s!", Disks[i]->name);
116
117	/* Put the freebsd chunks first */
118	for (c1 = Disks[i]->chunks->part; c1; c1 = c1->next) {
119	    if (c1->type == freebsd) {
120		fbsd_chunk_info[j].type = PART_SLICE;
121		fbsd_chunk_info[j].d = Disks[i];
122		fbsd_chunk_info[j].c = c1;
123		++j;
124	    }
125	}
126    }
127    for (i = 0; Disks[i]; i++) {
128	/* Then buzz through and pick up the partitions */
129	for (c1 = Disks[i]->chunks->part; c1; c1 = c1->next) {
130	    if (c1->type == freebsd) {
131		for (c2 = c1->part; c2; c2 = c2->next) {
132		    if (c2->type == part) {
133			if (c2->subtype == FS_SWAP)
134			    fbsd_chunk_info[j].type = PART_SWAP;
135			else
136			    fbsd_chunk_info[j].type = PART_FILESYSTEM;
137			fbsd_chunk_info[j].d = Disks[i];
138			fbsd_chunk_info[j].c = c2;
139			++j;
140		    }
141		}
142	    }
143	}
144    }
145    fbsd_chunk_info[j].d = NULL;
146    fbsd_chunk_info[j].c = NULL;
147    if (current_chunk >= j)
148	current_chunk = j  ? j - 1 : 0;
149}
150
151static PartInfo *
152new_part(char *mpoint, Boolean newfs)
153{
154    PartInfo *ret;
155
156    ret = (PartInfo *)safe_malloc(sizeof(PartInfo));
157    strncpy(ret->mountpoint, mpoint, FILENAME_MAX);
158    strcpy(ret->newfs_cmd, "newfs");
159    ret->newfs = newfs;
160    return ret;
161}
162
163PartInfo *
164get_mountpoint(struct chunk *c)
165{
166    char *val;
167    PartInfo *tmp;
168
169    val = msgGetInput(c && c->private ?
170		      ((PartInfo *)c->private)->mountpoint : NULL,
171		      "Please specify a mount point for the partition");
172    if (val) {
173	if (check_conflict(val)) {
174	    msgConfirm("You already have a mount point for %s assigned!", val);
175	    return NULL;
176	}
177	else if (*val != '/') {
178	    msgConfirm("Mount point must start with a / character");
179	    return NULL;
180	}
181	else if (!strcmp(val, "/")) {
182	    if (c) {
183	    	if (c->flags & CHUNK_PAST_1024) {
184		    msgConfirm("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!");
185		    return NULL;
186		}
187#if 0	/* This never seems to be set */
188		else if (!(c->flags & CHUNK_BSD_COMPAT)) {
189		    msgConfirm("This region cannot be used for your root partition as\nthe FreeBSD boot code cannot deal with a root partition created in\nsuch a region.  Please choose another partition for this.");
190		    return NULL;
191		}
192#endif
193		else
194		    c->flags |= CHUNK_IS_ROOT;
195	    }
196	}
197	else if (c)
198	    c->flags &= ~CHUNK_IS_ROOT;
199	safe_free(c ? c->private : NULL);
200	tmp = new_part(val, TRUE);
201	if (c) {
202	    c->private = tmp;
203	    c->private_free = safe_free;
204	}
205	return tmp;
206    }
207    return NULL;
208}
209
210static PartType
211get_partition_type(void)
212{
213    char selection[20];
214    static unsigned char *fs_types[] = {
215	"FS",
216	"A file system",
217	"Swap",
218	"A swap partition.",
219    };
220
221    if (!dialog_menu("Please choose a partition type",
222		    "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)) {
223	if (!strcmp(selection, "FS"))
224	    return PART_FILESYSTEM;
225	else if (!strcmp(selection, "Swap"))
226	    return PART_SWAP;
227    }
228    return PART_NONE;
229}
230
231static void
232getNewfsCmd(PartInfo *p)
233{
234    char *val;
235
236    val = msgGetInput(p->newfs_cmd,
237		      "Please enter the newfs command and options you'd like to use in\ncreating this file system.");
238    if (val)
239	strncpy(p->newfs_cmd, val, NEWFS_CMD_MAX);
240}
241
242
243#define MAX_MOUNT_NAME	12
244
245#define PART_PART_COL	0
246#define PART_MOUNT_COL	8
247#define PART_SIZE_COL	(PART_MOUNT_COL + MAX_MOUNT_NAME + 3)
248#define PART_NEWFS_COL	(PART_SIZE_COL + 7)
249#define PART_OFF	38
250
251/* How many mounted partitions to display in column before going to next */
252#define CHUNK_COLUMN_MAX	6
253
254static void
255print_fbsd_chunks(void)
256{
257    int i, j, srow, prow, pcol;
258    int sz;
259
260    attrset(A_REVERSE);
261    mvaddstr(0, 25, "FreeBSD Partition Editor");
262    attrset(A_NORMAL);
263
264    for (i = 0; i < 2; i++) {
265	attrset(A_UNDERLINE);
266	mvaddstr(CHUNK_PART_START_ROW - 1, PART_PART_COL + (i * PART_OFF),
267		 "Part");
268	attrset(A_NORMAL);
269
270	attrset(A_UNDERLINE);
271	mvaddstr(CHUNK_PART_START_ROW - 1, PART_MOUNT_COL + (i * PART_OFF),
272		 "Mount");
273	attrset(A_NORMAL);
274
275	attrset(A_UNDERLINE);
276	mvaddstr(CHUNK_PART_START_ROW - 1, PART_SIZE_COL + (i * PART_OFF) + 2,
277		 "Size");
278	attrset(A_NORMAL);
279
280	attrset(A_UNDERLINE);
281	mvaddstr(CHUNK_PART_START_ROW - 1, PART_NEWFS_COL + (i * PART_OFF),
282		 "Newfs");
283	attrset(A_NORMAL);
284    }
285
286    srow = CHUNK_SLICE_START_ROW;
287    prow = CHUNK_PART_START_ROW;
288    pcol = 0;
289
290    for (i = 0; fbsd_chunk_info[i].d; i++) {
291	if (i == current_chunk)
292	    attrset(A_REVERSE);
293	/* Is it a slice entry displayed at the top? */
294	if (fbsd_chunk_info[i].type == PART_SLICE) {
295	    sz = space_free(fbsd_chunk_info[i].c);
296	    mvprintw(srow++, 0,
297		     "Disk: %s\tPartition name: %s\tFree: %d blocks (%dMB)",
298		     fbsd_chunk_info[i].d->name,
299		     fbsd_chunk_info[i].c->name, sz, (sz / 2048));
300	}
301	/* Otherwise it's a swap or filesystem entry, at the bottom */
302	else {
303	    char onestr[PART_OFF], num[10], *mountpoint, *newfs;
304
305	    memset(onestr, ' ', PART_OFF - 1);
306	    onestr[PART_OFF - 1] = '\0';
307	    /* Go for two columns */
308	    if (prow == (CHUNK_PART_START_ROW + CHUNK_COLUMN_MAX)) {
309		pcol = PART_OFF;
310		prow = CHUNK_PART_START_ROW;
311	    }
312	    memcpy(onestr + PART_PART_COL, fbsd_chunk_info[i].c->name,
313		   strlen(fbsd_chunk_info[i].c->name));
314	    if (fbsd_chunk_info[i].type == PART_FILESYSTEM) {
315		if (fbsd_chunk_info[i].c->private) {
316		    mountpoint = ((PartInfo *)fbsd_chunk_info[i].c->private)->mountpoint;
317		    newfs = ((PartInfo *)fbsd_chunk_info[i].c->private)->newfs ? "Y" : "N";
318		}
319		else {
320		    fbsd_chunk_info[i].c->private = new_part("", FALSE);
321		    fbsd_chunk_info[i].c->private_free = safe_free;
322		    mountpoint = " ";
323		    newfs = "N";
324		}
325	    }
326	    else {
327		mountpoint = "swap";
328		newfs = " ";
329	    }
330	    for (j = 0; j < MAX_MOUNT_NAME && mountpoint[j]; j++)
331		onestr[PART_MOUNT_COL + j] = mountpoint[j];
332	    snprintf(num, 10, "%4ldMB", fbsd_chunk_info[i].c->size ?
333		    fbsd_chunk_info[i].c->size / 2048 : 0);
334	    memcpy(onestr + PART_SIZE_COL, num, strlen(num));
335	    memcpy(onestr + PART_NEWFS_COL, newfs, strlen(newfs));
336	    onestr[PART_NEWFS_COL + strlen(newfs)] = '\0';
337	    mvaddstr(prow, pcol, onestr);
338	    ++prow;
339	}
340	if (i == current_chunk)
341	    attrset(A_NORMAL);
342    }
343}
344
345static void
346print_command_summary()
347{
348    mvprintw(17, 0,
349	     "The following commands are valid here (upper or lower case):");
350    mvprintw(19, 0, "C = Create Partition   D = Delete Partition   M = Mount Partition");
351    mvprintw(20, 0, "N = Newfs Options      T = Toggle Newfs       ESC = Finish Partitioning");
352    mvprintw(21, 0, "The default target will be displayed in ");
353
354    attrset(A_REVERSE);
355    addstr("reverse video.");
356    attrset(A_NORMAL);
357    mvprintw(22, 0, "Use F1 or ? to get more help, arrow keys to move.");
358    move(0, 0);
359}
360
361void
362partition_disks(void)
363{
364    int sz, key = 0;
365    Boolean partitioning;
366    char *msg = NULL;
367    PartInfo *p;
368    PartType type;
369
370    dialog_clear();
371    partitioning = TRUE;
372    keypad(stdscr, TRUE);
373    record_fbsd_chunks();
374
375    while (partitioning) {
376	clear();
377	print_fbsd_chunks();
378	print_command_summary();
379	if (msg) {
380	    attrset(A_REVERSE); mvprintw(23, 0, msg); attrset(A_NORMAL);
381	    beep();
382	    msg = NULL;
383	}
384	refresh();
385	key = toupper(getch());
386	switch (key) {
387
388	case KEY_UP:
389	case '-':
390	    if (current_chunk != 0)
391		--current_chunk;
392	    break;
393
394	case KEY_DOWN:
395	case '+':
396	case '\r':
397	case '\n':
398	    if (fbsd_chunk_info[current_chunk + 1].d)
399		++current_chunk;
400	    break;
401
402	case KEY_HOME:
403	    current_chunk = 0;
404	    break;
405
406	case KEY_END:
407	    while (fbsd_chunk_info[current_chunk + 1].d)
408		++current_chunk;
409	    break;
410
411	case KEY_F(1):
412	case '?':
413	    systemDisplayFile("partitioning.hlp");
414	    break;
415
416	case 'C':
417	    if (fbsd_chunk_info[current_chunk].type != PART_SLICE) {
418		msg = "You can only do this in a master partition (see top of screen)";
419		break;
420	    }
421	    sz = space_free(fbsd_chunk_info[current_chunk].c);
422	    if (sz <= FS_MIN_SIZE)
423		msg = "Not enough space to create additional FreeBSD partition";
424	    else {
425		char *val, *cp, tmp[20];
426		int size;
427
428		snprintf(tmp, 20, "%d", sz);
429		val = msgGetInput(tmp, "Please specify the size for new FreeBSD partition in blocks, or append\na trailing `M' for megabytes (e.g. 20M).");
430		if (val && (size = strtol(val, &cp, 0)) > 0) {
431		    struct chunk *tmp;
432		    u_long flags = 0;
433
434		    if (*cp && toupper(*cp) == 'M')
435			size *= 2048;
436
437		    type = get_partition_type();
438		    if (type == PART_NONE)
439			break;
440		    else if (type == PART_FILESYSTEM) {
441			if ((p = get_mountpoint(NULL)) == NULL)
442			    break;
443			else if (!strcmp(p->mountpoint, "/"))
444			    flags |= CHUNK_IS_ROOT;
445			else
446			    flags &= ~CHUNK_IS_ROOT;
447		    }
448		    else
449			p = NULL;
450
451		    tmp = Create_Chunk_DWIM(fbsd_chunk_info[current_chunk].d,
452					    fbsd_chunk_info[current_chunk].c,
453					    size,
454					    part,
455					    (type == PART_SWAP) ?
456					    FS_SWAP : FS_BSDFFS,
457					    flags);
458		    if (!tmp)
459			msgConfirm("Unable to create the partition.  Too big?");
460		    else {
461			tmp->private = p;
462			tmp->private_free = safe_free;
463			record_fbsd_chunks();
464		    }
465		}
466	    }
467	    break;
468
469	case 'D':	/* delete */
470	    if (fbsd_chunk_info[current_chunk].type == PART_SLICE) {
471		msg = MSG_NOT_APPLICABLE;
472		break;
473	    }
474	    Delete_Chunk(fbsd_chunk_info[current_chunk].d,
475			 fbsd_chunk_info[current_chunk].c);
476	    record_fbsd_chunks();
477	    break;
478
479	case 'M':	/* mount */
480	    switch(fbsd_chunk_info[current_chunk].type) {
481	    case PART_SLICE:
482		msg = MSG_NOT_APPLICABLE;
483		break;
484
485	    case PART_SWAP:
486		msg = "You don't need to specify a mountpoint for a swap partition.";
487		break;
488
489	    case PART_FILESYSTEM:
490		p = get_mountpoint(fbsd_chunk_info[current_chunk].c);
491		if (p) {
492		    p->newfs = FALSE;
493		    record_fbsd_chunks();
494		}
495		break;
496
497	    default:
498		msgFatal("Bogus partition under cursor???");
499		break;
500	    }
501	    break;
502
503	case 'N':	/* Set newfs options */
504	    if (fbsd_chunk_info[current_chunk].c->private &&
505		((PartInfo *)fbsd_chunk_info[current_chunk].c->private)->newfs)
506		getNewfsCmd(fbsd_chunk_info[current_chunk].c->private);
507	    else
508		msg = MSG_NOT_APPLICABLE;
509	    break;
510
511	case 'T':	/* Toggle newfs state */
512	    if (fbsd_chunk_info[current_chunk].c->private)
513		((PartInfo *)fbsd_chunk_info[current_chunk].c->private)->newfs = !((PartInfo *)fbsd_chunk_info[current_chunk].c->private)->newfs;
514	    else
515		msg = MSG_NOT_APPLICABLE;
516	    break;
517
518	case 'W':
519	    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!")) {
520		int i;
521
522		clear();
523		dialog_clear();
524		end_dialog();
525		DialogActive = FALSE;
526		for (i = 0; Disks[i]; i++)
527		    slice_wizard(Disks[i]);
528		clear();
529		dialog_clear();
530		DialogActive = TRUE;
531		record_fbsd_chunks();
532	    }
533	    else
534		msg = "A most prudent choice!";
535	    break;
536
537	case 27:	/* ESC */
538	    partitioning = FALSE;
539	    break;
540
541	default:
542	    beep();
543	    msg = "Type F1 or ? for help";
544	    break;
545	}
546    }
547}
548
549int
550write_disks(void)
551{
552    int i;
553    extern u_char boot1[], boot2[];
554    extern u_char mbr[], bteasy17[];
555
556    dialog_clear();
557    for (i = 0; Disks[i]; i++) {
558	Set_Boot_Blocks(Disks[i], boot1, boot2);
559	dialog_clear();
560	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."))
561	    Set_Boot_Mgr(Disks[i], bteasy17);
562	else {
563	    dialog_clear();
564	    if (i == 0 && !msgYesNo("Would you like to remove an existing boot manager?"))
565		Set_Boot_Mgr(Disks[i], mbr);
566	}
567	dialog_clear();
568	if (!msgYesNo("Last Chance!  Are you sure you want to write out\nall these changes to disk?")) {
569	    Write_Disk(Disks[i]);
570	    return 0;
571	}
572    }
573    return 1;
574}
575