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