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