disks.c revision 8340
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.9 1995/05/08 00:38:02 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(struct disk **disks)
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 mountpoint for %s assigned!", val);
222	    return NULL;
223	}
224	else if (!strcmp(val, "/")) {
225	    if (c && c->flags & CHUNK_PAST_1024) {
226msgConfirm("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!");
227		return NULL;
228	    }
229	    else if (c)
230		c->flags |= CHUNK_IS_ROOT;
231	}
232	else if (c)
233	    c->flags &= ~CHUNK_IS_ROOT;
234	safe_free(c ? c->private : NULL);
235	tmp = new_part(val, TRUE);
236	if (c) {
237	    c->private = tmp;
238	    c->private_free = safe_free;
239	}
240	return tmp;
241    }
242    return NULL;
243}
244
245static PartType
246get_partition_type(void)
247{
248    char selection[20];
249    static unsigned char *fs_types[] = {
250	"FS",
251	"A file system",
252	"Swap",
253	"A swap partition.",
254    };
255
256    if (!dialog_menu("Please choose a partition type",
257		    "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)) {
258	if (!strcmp(selection, "FS"))
259	    return PART_FILESYSTEM;
260	else if (!strcmp(selection, "Swap"))
261	    return PART_SWAP;
262    }
263    return PART_NONE;
264}
265
266static void
267getNewfsCmd(PartInfo *p)
268{
269    char *val;
270
271    val = msgGetInput(p->newfs_cmd,
272		      "Please enter the newfs command and options you'd like to use in\ncreating this file system.");
273    if (val)
274	strncpy(p->newfs_cmd, val, NEWFS_CMD_MAX);
275}
276
277
278#define MAX_MOUNT_NAME	12
279
280#define PART_PART_COL	0
281#define PART_MOUNT_COL	8
282#define PART_SIZE_COL	(PART_MOUNT_COL + MAX_MOUNT_NAME + 3)
283#define PART_NEWFS_COL	(PART_SIZE_COL + 7)
284#define PART_OFF	38
285
286/* How many mounted partitions to display in column before going to next */
287#define CHUNK_COLUMN_MAX	6
288
289static void
290print_fbsd_chunks(void)
291{
292    int i, j, srow, prow, pcol;
293    int sz;
294
295    attrset(A_REVERSE);
296    mvaddstr(0, 25, "FreeBSD Partition Editor");
297    attrset(A_NORMAL);
298
299    for (i = 0; i < 2; i++) {
300	attrset(A_UNDERLINE);
301	mvaddstr(CHUNK_PART_START_ROW - 1, PART_PART_COL + (i * PART_OFF),
302		 "Part");
303	attrset(A_NORMAL);
304
305	attrset(A_UNDERLINE);
306	mvaddstr(CHUNK_PART_START_ROW - 1, PART_MOUNT_COL + (i * PART_OFF),
307		 "Mount");
308	attrset(A_NORMAL);
309
310	attrset(A_UNDERLINE);
311	mvaddstr(CHUNK_PART_START_ROW - 1, PART_SIZE_COL + (i * PART_OFF) + 2,
312		 "Size");
313	attrset(A_NORMAL);
314
315	attrset(A_UNDERLINE);
316	mvaddstr(CHUNK_PART_START_ROW - 1, PART_NEWFS_COL + (i * PART_OFF),
317		 "Newfs");
318	attrset(A_NORMAL);
319    }
320
321    srow = CHUNK_SLICE_START_ROW;
322    prow = CHUNK_PART_START_ROW;
323    pcol = 0;
324
325    for (i = 0; fbsd_chunk_info[i].d; i++) {
326	if (i == current_chunk)
327	    attrset(A_REVERSE);
328	/* Is it a slice entry displayed at the top? */
329	if (fbsd_chunk_info[i].type == PART_SLICE) {
330	    sz = space_free(fbsd_chunk_info[i].c);
331	    mvprintw(srow++, 0,
332		     "Disk: %s\tPartition name: %s\tFree: %d blocks (%dMB)",
333		     fbsd_chunk_info[i].d->name,
334		     fbsd_chunk_info[i].c->name, sz, (sz / 2048));
335	}
336	/* Otherwise it's a swap or filesystem entry, at the bottom */
337	else {
338	    char onestr[PART_OFF], num[10], *mountpoint, *newfs;
339
340	    memset(onestr, ' ', PART_OFF - 1);
341	    onestr[PART_OFF - 1] = '\0';
342	    /* Go for two columns */
343	    if (prow == (CHUNK_PART_START_ROW + CHUNK_COLUMN_MAX)) {
344		pcol = PART_OFF;
345		prow = CHUNK_PART_START_ROW;
346	    }
347	    memcpy(onestr + PART_PART_COL, fbsd_chunk_info[i].c->name,
348		   strlen(fbsd_chunk_info[i].c->name));
349	    if (fbsd_chunk_info[i].type == PART_FILESYSTEM) {
350		if (fbsd_chunk_info[i].c->private) {
351		    mountpoint = ((PartInfo *)fbsd_chunk_info[i].c->private)->mountpoint;
352		    newfs = ((PartInfo *)fbsd_chunk_info[i].c->private)->newfs ? "Y" : "N";
353		}
354		else {
355		    fbsd_chunk_info[i].c->private = new_part("", FALSE);
356		    fbsd_chunk_info[i].c->private_free = safe_free;
357		    mountpoint = " ";
358		    newfs = "N";
359		}
360	    }
361	    else {
362		mountpoint = "swap";
363		newfs = " ";
364	    }
365	    for (j = 0; j < MAX_MOUNT_NAME && mountpoint[j]; j++)
366		onestr[PART_MOUNT_COL + j] = mountpoint[j];
367	    sprintf(num, "%4ldMB", fbsd_chunk_info[i].c->size ?
368		    fbsd_chunk_info[i].c->size / 2048 : 0);
369	    memcpy(onestr + PART_SIZE_COL, num, strlen(num));
370	    memcpy(onestr + PART_NEWFS_COL, newfs, strlen(newfs));
371	    mvaddstr(prow, pcol, onestr);
372	    ++prow;
373	}
374	if (i == current_chunk)
375	    attrset(A_NORMAL);
376    }
377}
378
379static void
380print_command_summary()
381{
382    mvprintw(17, 0,
383	     "The following commands are valid here (upper or lower case):");
384    mvprintw(19, 0, "C = Create Partition   D = Delete Partition   M = Mount Partition");
385    mvprintw(20, 0, "N = Newfs Options      T = Toggle Newfs       ESC = Finish Partitioning");
386    mvprintw(21, 0, "The default target will be displayed in ");
387
388    attrset(A_REVERSE);
389    addstr("reverse video");
390    attrset(A_NORMAL);
391    mvprintw(22, 0, "Use F1 or ? to get more help");
392    move(0, 0);
393}
394
395void
396partition_disks(struct disk **disks)
397{
398    int sz, key = 0;
399    Boolean partitioning;
400    char *msg = NULL;
401    PartInfo *p;
402    PartType type;
403
404    dialog_clear();
405    partitioning = TRUE;
406    keypad(stdscr, TRUE);
407    record_fbsd_chunks(disks);
408
409    while (partitioning) {
410	clear();
411	print_fbsd_chunks();
412	print_command_summary();
413	if (msg) {
414	    attrset(A_REVERSE); mvprintw(23, 0, msg); attrset(A_NORMAL);
415	    beep();
416	    msg = NULL;
417	}
418	refresh();
419	key = toupper(getch());
420	switch (key) {
421
422	case KEY_UP:
423	case '-':
424	    if (current_chunk != 0)
425		--current_chunk;
426	    break;
427
428	case KEY_DOWN:
429	case '+':
430	case '\r':
431	case '\n':
432	    if (fbsd_chunk_info[current_chunk + 1].d)
433		++current_chunk;
434	    break;
435
436	case KEY_HOME:
437	    current_chunk = 0;
438	    break;
439
440	case KEY_END:
441	    while (fbsd_chunk_info[current_chunk + 1].d)
442		++current_chunk;
443	    break;
444
445	case KEY_F(1):
446	case '?':
447	    systemDisplayFile("partitioning.hlp");
448	    break;
449
450	case 'C':
451	    if (fbsd_chunk_info[current_chunk].type != PART_SLICE) {
452		msg = "You can only do this in a master partition (see top of screen)";
453		break;
454	    }
455	    sz = space_free(fbsd_chunk_info[current_chunk].c);
456	    if (sz <= FS_MIN_SIZE)
457		msg = "Not enough space to create additional FreeBSD partition";
458	    else {
459		char *val, *cp, tmp[20];
460		int size;
461
462		snprintf(tmp, 20, "%d", sz);
463		val = msgGetInput(tmp, "Please specify the size for new FreeBSD partition in blocks, or append\na trailing `M' for megabytes (e.g. 20M).");
464		if (val && (size = strtol(val, &cp, 0)) > 0) {
465		    struct chunk *tmp;
466		    u_long flags = 0;
467
468		    if (*cp && toupper(*cp) == 'M')
469			size *= 2048;
470
471		    type = get_partition_type();
472		    if (type == PART_NONE)
473			break;
474		    else if (type == PART_FILESYSTEM) {
475			if ((p = get_mountpoint(NULL)) == NULL)
476			    break;
477			else if (!strcmp(p->mountpoint, "/"))
478			    flags |= CHUNK_IS_ROOT;
479			else
480			    flags &= ~CHUNK_IS_ROOT;
481		    }
482		    else
483			p = NULL;
484		    Create_Chunk(fbsd_chunk_info[current_chunk].d,
485				 fbsd_chunk_info[current_chunk].c->offset +
486				 sz - size,
487				 size,
488				 part,
489				 (type == PART_SWAP) ? FS_SWAP : FS_BSDFFS,
490				 flags);
491		    tmp = find_chunk_by_loc(fbsd_chunk_info[current_chunk].d,
492					    fbsd_chunk_info[current_chunk].c->offset + sz - size, size);
493		    if (!tmp)
494			msgConfirm("Unable to create the partition.  Too big?");
495		    else {
496			tmp->private = p;
497			tmp->private_free = safe_free;
498			record_fbsd_chunks(disks);
499		    }
500		}
501	    }
502	    break;
503
504	case 'D':	/* delete */
505	    if (fbsd_chunk_info[current_chunk].type == PART_SLICE) {
506		msg = MSG_NOT_APPLICABLE;
507		break;
508	    }
509	    Delete_Chunk(fbsd_chunk_info[current_chunk].d,
510			 fbsd_chunk_info[current_chunk].c);
511	    record_fbsd_chunks(disks);
512	    break;
513
514	case 'M':	/* mount */
515	    switch(fbsd_chunk_info[current_chunk].type) {
516	    case PART_SLICE:
517		msg = MSG_NOT_APPLICABLE;
518		break;
519
520	    case PART_SWAP:
521		msg = "You don't need to specify a mountpoint for a swap partition.";
522		break;
523
524	    case PART_FILESYSTEM:
525		p = get_mountpoint(fbsd_chunk_info[current_chunk].c);
526		if (p) {
527		    p->newfs = FALSE;
528		    record_fbsd_chunks(disks);
529		}
530		break;
531
532	    default:
533		msgFatal("Bogus partition under cursor???");
534		break;
535	    }
536	    break;
537
538	case 'N':	/* Set newfs options */
539	    if (fbsd_chunk_info[current_chunk].c->private &&
540		((PartInfo *)fbsd_chunk_info[current_chunk].c->private)->newfs)
541		getNewfsCmd(fbsd_chunk_info[current_chunk].c->private);
542	    else
543		msg = MSG_NOT_APPLICABLE;
544	    break;
545
546	case 'T':	/* Toggle newfs state */
547	    if (fbsd_chunk_info[current_chunk].c->private)
548		((PartInfo *)fbsd_chunk_info[current_chunk].c->private)->newfs = !((PartInfo *)fbsd_chunk_info[current_chunk].c->private)->newfs;
549	    else
550		msg = MSG_NOT_APPLICABLE;
551	    break;
552
553	case 'W':
554	    if (!msgYesNo("Are you sure you want to go into Wizard mode?\nThis is an entirely documented feature which you are not\nexpected to understand!")) {
555		int i;
556
557		clear();
558		dialog_clear();
559		end_dialog();
560		DialogActive = FALSE;
561		for (i = 0; disks[i]; i++)
562		    slice_wizard(disks[i]);
563		clear();
564		dialog_clear();
565		DialogActive = TRUE;
566		record_fbsd_chunks(disks);
567	    }
568	    else
569		msg = "A most prudent choice!";
570	    break;
571
572	case 27:	/* ESC */
573	    partitioning = FALSE;
574	    break;
575
576	default:
577	    beep();
578	    msg = "Type F1 or ? for help";
579	    break;
580	}
581    }
582}
583
584int
585write_disks(struct disk **disks)
586{
587    int i;
588    extern u_char boot1[], boot2[];
589    extern u_char mbr[], bteasy17[];
590
591    dialog_clear();
592    for (i = 0; disks[i]; i++) {
593	if (contains_root_partition(disks[i]))
594	    Set_Boot_Blocks(disks[i], boot1, boot2);
595	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."))
596	    Set_Boot_Mgr(disks[i], bteasy17);
597	else if (i == 0 && !msgYesNo("Would you like to remove an existing boot manager?"))
598	    Set_Boot_Mgr(disks[i], mbr);
599	if (!msgYesNo("Last Chance!  Are you sure you want to write out\nall these changes to disk?")) {
600	    Write_Disk(disks[i]);
601	}
602	return 0;
603    }
604    return 1;
605}
606