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