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