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