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