label.c revision 8810
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: label.c,v 1.26 1995/05/26 11:21:46 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 * Everything to do with editing the contents of disk labels.
50 */
51
52/* A nice message we use a lot in the disklabel editor */
53#define MSG_NOT_APPLICABLE	"That option is not applicable here"
54
55/* Where to start printing the freebsd slices */
56#define CHUNK_SLICE_START_ROW		2
57#define CHUNK_PART_START_ROW		11
58
59/* One MB worth of blocks */
60#define ONE_MEG				2048
61
62/* The smallest filesystem we're willing to create */
63#define FS_MIN_SIZE			ONE_MEG
64
65/* The smallest root filesystem we're willing to create */
66#define ROOT_MIN_SIZE			(20 * ONE_MEG)
67
68/* All the chunks currently displayed on the screen */
69static struct {
70    struct chunk *c;
71    PartType type;
72} label_chunk_info[MAX_CHUNKS + 1];
73static int here;
74
75/* See if we're already using a desired partition name */
76static Boolean
77check_conflict(char *name)
78{
79    int i;
80
81    for (i = 0; label_chunk_info[i].c; i++)
82	if (label_chunk_info[i].type == PART_FILESYSTEM
83	    && label_chunk_info[i].c->private
84	    && !strcmp(((PartInfo *)label_chunk_info[i].c->private)->mountpoint, name))
85	    return TRUE;
86    return FALSE;
87}
88
89/* How much space is in this FreeBSD slice? */
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
106/* Snapshot the current situation into the displayed chunks structure */
107static void
108record_label_chunks()
109{
110    int i, j, p;
111    struct chunk *c1, *c2;
112    Device **devs;
113    Disk *d;
114
115    devs = deviceFind(NULL, DEVICE_TYPE_DISK);
116    if (!devs) {
117	msgConfirm("No disks found!");
118	return;
119    }
120
121    j = p = 0;
122    /* First buzz through and pick up the FreeBSD slices */
123    for (i = 0; devs[i]; i++) {
124	if (!devs[i]->enabled)
125	    continue;
126	d = (Disk *)devs[i]->private;
127	if (!d->chunks)
128	    msgFatal("No chunk list found for %s!", d->name);
129
130	/* Put the slice entries first */
131	for (c1 = d->chunks->part; c1; c1 = c1->next) {
132	    if (c1->type == freebsd) {
133		label_chunk_info[j].type = PART_SLICE;
134		label_chunk_info[j].c = c1;
135		++j;
136	    }
137	}
138    }
139    /* Now run through again and get the FreeBSD partition entries */
140    for (i = 0; devs[i]; i++) {
141	if (!devs[i]->enabled)
142	    continue;
143	d = (Disk *)devs[i]->private;
144	/* Then buzz through and pick up the partitions */
145	for (c1 = d->chunks->part; c1; c1 = c1->next) {
146	    if (c1->type == freebsd) {
147		for (c2 = c1->part; c2; c2 = c2->next) {
148		    if (c2->type == part) {
149			if (c2->subtype == FS_SWAP)
150			    label_chunk_info[j].type = PART_SWAP;
151			else
152			    label_chunk_info[j].type = PART_FILESYSTEM;
153			label_chunk_info[j].c = c2;
154			++j;
155		    }
156		}
157	    }
158	    else if (c1->type == fat) {
159		label_chunk_info[j].type = PART_FAT;
160		label_chunk_info[j].c = c1;
161		++j;
162	    }
163	}
164    }
165    label_chunk_info[j].c = NULL;
166    if (here >= j)
167	here = j  ? j - 1 : 0;
168}
169
170/* A new partition entry */
171static PartInfo *
172new_part(char *mpoint, Boolean newfs, u_long size)
173{
174    PartInfo *ret;
175    u_long target,divisor;
176
177    ret = (PartInfo *)safe_malloc(sizeof(PartInfo));
178    strncpy(ret->mountpoint, mpoint, FILENAME_MAX);
179    strcpy(ret->newfs_cmd, "newfs");
180    ret->newfs = newfs;
181    if (!size)
182	    return ret;
183    for(target = size; target; target--) {
184	for(divisor = 4096 ; divisor > 1023; divisor--) {
185	    if (!(target % divisor)) {
186		sprintf(ret->newfs_cmd+strlen(ret->newfs_cmd),
187		    " -u %ld",divisor);
188		return ret;
189	    }
190	}
191    }
192    return ret;
193}
194
195/* Get the mountpoint for a partition and save it away */
196PartInfo *
197get_mountpoint(struct chunk *old)
198{
199    char *val;
200    PartInfo *tmp;
201
202    if (old && old->private)
203	tmp = old->private;
204    else
205	tmp = NULL;
206    val = msgGetInput(tmp ? tmp->mountpoint : NULL, "Please specify a mount point for the partition");
207    if (!val || !*val) {
208	if (!old)
209	    return NULL;
210	else {
211	    free(old->private);
212	    old->private = NULL;
213	}
214	return NULL;
215    }
216
217    /* Is it just the same value? */
218    if (tmp && !strcmp(tmp->mountpoint, val))
219	return NULL;
220
221    /* Did we use it already? */
222    if (check_conflict(val)) {
223	msgConfirm("You already have a mount point for %s assigned!", val);
224	return NULL;
225    }
226
227    /* Is it bogus? */
228    if (*val != '/') {
229	msgConfirm("Mount point must start with a / character");
230	return NULL;
231    }
232
233    /* Is it going to be mounted on root? */
234    if (!strcmp(val, "/")) {
235	if (old)
236	    old->flags |= CHUNK_IS_ROOT;
237    }
238    else if (old)
239	old->flags &= ~CHUNK_IS_ROOT;
240
241    safe_free(tmp);
242    tmp = new_part(val, TRUE, 0);
243    if (old) {
244	old->private = tmp;
245	old->private_free = safe_free;
246    }
247    return tmp;
248}
249
250/* Get the type of the new partiton */
251static PartType
252get_partition_type(void)
253{
254    char selection[20];
255    int i;
256
257    static unsigned char *fs_types[] = {
258	"FS",
259	"A file system",
260	"Swap",
261	"A swap partition.",
262    };
263    i = dialog_menu("Please choose a partition type",
264		    "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);
265    if (!i) {
266	if (!strcmp(selection, "FS"))
267	    return PART_FILESYSTEM;
268	else if (!strcmp(selection, "Swap"))
269	    return PART_SWAP;
270    }
271    return PART_NONE;
272}
273
274/* If the user wants a special newfs command for this, set it */
275static void
276getNewfsCmd(PartInfo *p)
277{
278    char *val;
279
280    val = msgGetInput(p->newfs_cmd,
281		      "Please enter the newfs command and options you'd like to use in\ncreating this file system.");
282    if (val)
283	strncpy(p->newfs_cmd, val, NEWFS_CMD_MAX);
284}
285
286
287#define MAX_MOUNT_NAME	12
288
289#define PART_PART_COL	0
290#define PART_MOUNT_COL	8
291#define PART_SIZE_COL	(PART_MOUNT_COL + MAX_MOUNT_NAME + 3)
292#define PART_NEWFS_COL	(PART_SIZE_COL + 7)
293#define PART_OFF	38
294
295/* How many mounted partitions to display in column before going to next */
296#define CHUNK_COLUMN_MAX	5
297
298/* stick this all up on the screen */
299static void
300print_label_chunks(void)
301{
302    int i, j, srow, prow, pcol;
303    int sz;
304
305    attrset(A_REVERSE);
306    mvaddstr(0, 25, "FreeBSD Disklabel Editor");
307    clrtobot();
308    attrset(A_NORMAL);
309
310    for (i = 0; i < 2; i++) {
311	mvaddstr(CHUNK_PART_START_ROW - 2, PART_PART_COL + (i * PART_OFF), "Part");
312	mvaddstr(CHUNK_PART_START_ROW - 1, PART_PART_COL + (i * PART_OFF), "----");
313
314	mvaddstr(CHUNK_PART_START_ROW - 2, PART_MOUNT_COL + (i * PART_OFF), "Mount");
315	mvaddstr(CHUNK_PART_START_ROW - 1, PART_MOUNT_COL + (i * PART_OFF), "-----");
316
317	mvaddstr(CHUNK_PART_START_ROW - 2, PART_SIZE_COL + (i * PART_OFF) + 2, "Size");
318	mvaddstr(CHUNK_PART_START_ROW - 1, PART_SIZE_COL + (i * PART_OFF) + 2, "----");
319
320	mvaddstr(CHUNK_PART_START_ROW - 2, PART_NEWFS_COL + (i * PART_OFF), "Newfs");
321	mvaddstr(CHUNK_PART_START_ROW - 1, PART_NEWFS_COL + (i * PART_OFF), "-----");
322    }
323    srow = CHUNK_SLICE_START_ROW;
324    prow = CHUNK_PART_START_ROW;
325    pcol = 0;
326
327    for (i = 0; label_chunk_info[i].c; i++) {
328	if (i == here)
329	    attrset(A_REVERSE);
330	/* Is it a slice entry displayed at the top? */
331	if (label_chunk_info[i].type == PART_SLICE) {
332	    sz = space_free(label_chunk_info[i].c);
333	    mvprintw(srow++, 0, "Disk: %s\tPartition name: %s\tFree: %d blocks (%dMB)",
334		     label_chunk_info[i].c->disk->name, label_chunk_info[i].c->name, sz, (sz / ONE_MEG));
335	}
336	/* Otherwise it's a DOS, swap or filesystem entry, at the bottom */
337	else {
338	    char onestr[PART_OFF], num[10], *mountpoint, *newfs;
339
340	    /*
341	     * We copy this into a blank-padded string so that it looks like
342	     * a solid bar in reverse-video
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, label_chunk_info[i].c->name, strlen(label_chunk_info[i].c->name));
352	    /* If it's a filesystem, display the mountpoint */
353	    if (label_chunk_info[i].type == PART_FILESYSTEM || label_chunk_info[i].type == PART_FAT) {
354		if (label_chunk_info[i].c->private == NULL) {
355		    static int mnt = 0;
356		    char foo[10];
357
358		    /*
359		     * Hmm!  A partition that must have already been here.
360		     * Fill in a fake mountpoint and register it
361		     */
362		    sprintf(foo, "/mnt%d", mnt++);
363		    label_chunk_info[i].c->private = new_part(foo, FALSE, label_chunk_info[i].c->size);
364		    label_chunk_info[i].c->private_free = safe_free;
365		}
366		mountpoint = ((PartInfo *)label_chunk_info[i].c->private)->mountpoint;
367		if (label_chunk_info[i].type == PART_FAT)
368		    newfs = "DOS";
369		else
370		    newfs = ((PartInfo *)label_chunk_info[i].c->private)->newfs ? "Y" : "N";
371	    }
372	    else if (label_chunk_info[i].type == PART_SWAP) {
373		mountpoint = "swap";
374		newfs = " ";
375	    }
376	    else {
377		mountpoint = "<NONE>";
378		newfs = "*";
379	    }
380	    for (j = 0; j < MAX_MOUNT_NAME && mountpoint[j]; j++)
381		onestr[PART_MOUNT_COL + j] = mountpoint[j];
382	    snprintf(num, 10, "%4ldMB", label_chunk_info[i].c->size ? label_chunk_info[i].c->size / ONE_MEG : 0);
383	    memcpy(onestr + PART_SIZE_COL, num, strlen(num));
384	    memcpy(onestr + PART_NEWFS_COL, newfs, strlen(newfs));
385	    onestr[PART_NEWFS_COL + strlen(newfs)] = '\0';
386	    mvaddstr(prow, pcol, onestr);
387	    ++prow;
388	}
389	if (i == here)
390	    attrset(A_NORMAL);
391    }
392}
393
394static void
395print_command_summary()
396{
397    mvprintw(17, 0,
398	     "The following commands are valid here (upper or lower case):");
399    mvprintw(19, 0, "C = Create Partition   D = Delete Partition   M = Mount Partition");
400    mvprintw(20, 0, "N = Newfs Options      T = Toggle Newfs       ESC = Exit this screen");
401    mvprintw(21, 0, "The default target will be displayed in ");
402
403    attrset(A_REVERSE);
404    addstr("reverse");
405    attrset(A_NORMAL);
406    addstr(" video.");
407    mvprintw(22, 0, "Use F1 or ? to get more help, arrow keys to move.");
408    move(0, 0);
409}
410
411int
412diskLabelEditor(char *str)
413{
414    int sz, key = 0;
415    Boolean labeling;
416    char *msg = NULL;
417    PartInfo *p;
418    PartType type;
419
420    labeling = TRUE;
421    keypad(stdscr, TRUE);
422    record_label_chunks();
423
424    if (!getenv(DISK_PARTITIONED)) {
425	msgConfirm("You need to partition your disk(s) before you can assign disk labels.");
426	return 0;
427    }
428    dialog_clear(); clear();
429    while (labeling) {
430	clear();
431	print_label_chunks();
432	print_command_summary();
433	if (msg) {
434	    attrset(A_REVERSE); mvprintw(23, 0, msg); attrset(A_NORMAL);
435	    beep();
436	    msg = NULL;
437	}
438	refresh();
439	key = toupper(getch());
440	switch (key) {
441
442	case '\014':	/* ^L */
443	    continue;
444
445	case KEY_UP:
446	case '-':
447	    if (here != 0)
448		--here;
449	    else
450		while (label_chunk_info[here + 1].c)
451		    ++here;
452	    break;
453
454	case KEY_DOWN:
455	case '+':
456	case '\r':
457	case '\n':
458	    if (label_chunk_info[here + 1].c)
459		++here;
460	    else
461		here = 0;
462	    break;
463
464	case KEY_HOME:
465	    here = 0;
466	    break;
467
468	case KEY_END:
469	    while (label_chunk_info[here + 1].c)
470		++here;
471	    break;
472
473	case KEY_F(1):
474	case '?':
475	    systemDisplayFile("disklabel.hlp");
476	    break;
477
478	case 'C':
479	    if (label_chunk_info[here].type != PART_SLICE) {
480		msg = "You can only do this in a master partition (see top of screen)";
481		break;
482	    }
483	    sz = space_free(label_chunk_info[here].c);
484	    if (sz <= FS_MIN_SIZE) {
485		msg = "Not enough space to create additional FreeBSD partition";
486		break;
487	    }
488	    {
489		char *val, *cp;
490		int size;
491		struct chunk *tmp;
492		u_long flags = 0;
493
494		val = msgGetInput(NULL, "Please specify the size for new FreeBSD partition in blocks, or\nappend a trailing `M' for megabytes (e.g. 20M), `C' for cylinders\nor `%%' for a percentage of remaining space.\n\nSpace free is %d blocks (%dMB)", sz, sz / ONE_MEG);
495		if (!val || (size = strtol(val, &cp, 0)) <= 0)
496		    break;
497
498		if (sz <= FS_MIN_SIZE) {
499		    msgConfirm("The minimum filesystem size is %dMB", FS_MIN_SIZE / ONE_MEG);
500		    break;
501		}
502		if (*cp) {
503		    if (toupper(*cp) == 'M')
504			size *= ONE_MEG;
505		    else if (toupper(*cp) == 'C')
506			size *= (label_chunk_info[here].c->disk->bios_hd * label_chunk_info[here].c->disk->bios_sect);
507		    else if (*cp == '%') {
508			float fsz, fsize;
509
510			fsz = (float)sz;
511			fsize = (float)size;
512			fsize *= 0.10;
513			fsz *= fsize;
514			size = (int)fsz;
515		    }
516		}
517		type = get_partition_type();
518		if (type == PART_NONE)
519		    break;
520
521		if (type == PART_FILESYSTEM) {
522		    if ((p = get_mountpoint(NULL)) == NULL)
523			break;
524		    else if (!strcmp(p->mountpoint, "/"))
525			flags |= CHUNK_IS_ROOT;
526		    else
527			flags &= ~CHUNK_IS_ROOT;
528		} else
529		    p = NULL;
530
531		if ((flags & CHUNK_IS_ROOT)) {
532		    if (!(label_chunk_info[here].c->flags & CHUNK_BSD_COMPAT)) {
533			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 location.  Please choose another location for your root\npartition and try again!");
534			break;
535		    }
536		    if (size < ROOT_MIN_SIZE)
537			msgConfirm("Warning: This is smaller than the recommended size for a\nroot partition.  For a variety of reasons, root\npartitions should usually be at least %dMB in size", ROOT_MIN_SIZE / ONE_MEG);
538		}
539		tmp = Create_Chunk_DWIM(label_chunk_info[here].c->disk,
540					label_chunk_info[here].c,
541					size, part,
542					(type == PART_SWAP) ? FS_SWAP : FS_BSDFFS,
543					flags);
544		if (!tmp) {
545		    msgConfirm("Unable to create the partition. Too big?");
546		    break;
547		}
548		if ((flags & CHUNK_IS_ROOT) && (tmp->flags & CHUNK_PAST_1024)) {
549		    msgConfirm("This region cannot be used for your root partition as it starts\nor extends past the 1024'th cylinder mark and is thus a\npoor location to boot from.  Please choose another\nlocation for your root partition and try again!");
550		    Delete_Chunk(label_chunk_info[here].c->disk, tmp);
551		    break;
552		}
553		if (type != PART_SWAP) {
554		    /* This is needed to tell the newfs -u about the size */
555		    tmp->private = new_part(p->mountpoint,p->newfs,tmp->size);
556		    safe_free(p);
557		} else {
558		    tmp->private = p;
559		}
560		tmp->private_free = safe_free;
561		record_label_chunks();
562	    }
563	    break;
564
565	case 'D':	/* delete */
566	    if (label_chunk_info[here].type == PART_SLICE) {
567		msg = MSG_NOT_APPLICABLE;
568		break;
569	    }
570	    else if (label_chunk_info[here].type == PART_FAT) {
571		msg = "Use the Disk Partition Editor to delete DOS partitions";
572		break;
573	    }
574	    Delete_Chunk(label_chunk_info[here].c->disk, label_chunk_info[here].c);
575	    record_label_chunks();
576	    break;
577
578	case 'M':	/* mount */
579	    switch(label_chunk_info[here].type) {
580	    case PART_SLICE:
581		msg = MSG_NOT_APPLICABLE;
582		break;
583
584	    case PART_SWAP:
585		msg = "You don't need to specify a mountpoint for a swap partition.";
586		break;
587
588	    case PART_FAT:
589	    case PART_FILESYSTEM:
590		p = get_mountpoint(label_chunk_info[here].c);
591		if (p) {
592		    p->newfs = FALSE;
593		    if (label_chunk_info[here].type == PART_FAT
594			&& (!strcmp(p->mountpoint, "/") || !strcmp(p->mountpoint, "/usr")
595			    || !strcmp(p->mountpoint, "/var"))) {
596			msgConfirm("%s is an invalid mount point for a DOS partition!", p->mountpoint);
597			strcpy(p->mountpoint, "/bogus");
598		    }
599		}
600		record_label_chunks();
601		break;
602
603	    default:
604		msgFatal("Bogus partition under cursor???");
605		break;
606	    }
607	    break;
608
609	case 'N':	/* Set newfs options */
610	    if (label_chunk_info[here].c->private &&
611		((PartInfo *)label_chunk_info[here].c->private)->newfs)
612		getNewfsCmd(label_chunk_info[here].c->private);
613	    else
614		msg = MSG_NOT_APPLICABLE;
615	    break;
616
617	case 'T':	/* Toggle newfs state */
618	    if (label_chunk_info[here].type == PART_FILESYSTEM &&
619		label_chunk_info[here].c->private) {
620		    PartInfo *pi = ((PartInfo *)
621			label_chunk_info[here].c->private);
622		    label_chunk_info[here].c->private = new_part(
623			pi->mountpoint,
624			!pi->newfs,
625			label_chunk_info[here].c->size);
626		    safe_free(pi);
627		}
628	    else
629		msg = MSG_NOT_APPLICABLE;
630	    break;
631
632	case 'W':
633	    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!")) {
634		int i;
635		Device **devs;
636
637		dialog_clear();
638		end_dialog();
639		DialogActive = FALSE;
640		devs = deviceFind(NULL, DEVICE_TYPE_DISK);
641		if (!devs) {
642		    msgConfirm("Can't find any disk devicse!");
643		    break;
644		}
645		for (i = 0; devs[i] && ((Disk *)devs[i]->private); i++) {
646		    if (devs[i]->enabled)
647		    	slice_wizard(((Disk *)devs[i]->private));
648		}
649		DialogActive = TRUE;
650		dialog_clear();
651		record_label_chunks();
652	    }
653	    else
654		msg = "A most prudent choice!";
655	    break;
656
657	case 27:	/* ESC */
658	    labeling = FALSE;
659	    break;
660
661	default:
662	    beep();
663	    msg = "Type F1 or ? for help";
664	    break;
665	}
666    }
667    variable_set2(DISK_LABELLED, "yes");
668    dialog_clear();
669    return 0;
670}
671
672
673
674