label.c revision 8641
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.10 1995/05/19 02:19:15 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/* 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)
182{
183    PartInfo *ret;
184
185    ret = (PartInfo *)safe_malloc(sizeof(PartInfo));
186    strncpy(ret->mountpoint, mpoint, FILENAME_MAX);
187    strcpy(ret->newfs_cmd, "newfs");
188    ret->newfs = newfs;
189    return ret;
190}
191
192/* Get the mountpoint for a partition and save it away */
193PartInfo *
194get_mountpoint(struct chunk *old)
195{
196    char *val;
197    PartInfo *tmp;
198
199    val = msgGetInput(old && old->private ? ((PartInfo *)old->private)->mountpoint : NULL,
200		      "Please specify a mount point for the partition");
201    if (val) {
202	/* Is it just the same value? */
203	if (old && old->private && !strcmp(((PartInfo *)old->private)->mountpoint, val))
204	    return NULL;
205	if (check_conflict(val)) {
206	    msgConfirm("You already have a mount point for %s assigned!", val);
207	    return NULL;
208	}
209	else if (*val != '/') {
210	    msgConfirm("Mount point must start with a / character");
211	    return NULL;
212	}
213	else if (!strcmp(val, "/")) {
214	    if (old)
215		old->flags |= CHUNK_IS_ROOT;
216	}
217	else if (old)
218	    old->flags &= ~CHUNK_IS_ROOT;
219	safe_free(old ? old->private : NULL);
220	tmp = new_part(val, TRUE);
221	if (old) {
222	    old->private = tmp;
223	    old->private_free = safe_free;
224	}
225	return tmp;
226    }
227    return NULL;
228}
229
230/* Get the type of the new partiton */
231static PartType
232get_partition_type(void)
233{
234    char selection[20];
235    static unsigned char *fs_types[] = {
236	"FS",
237	"A file system",
238	"Swap",
239	"A swap partition.",
240    };
241
242    if (!dialog_menu("Please choose a partition type",
243		    "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)) {
244	if (!strcmp(selection, "FS"))
245	    return PART_FILESYSTEM;
246	else if (!strcmp(selection, "Swap"))
247	    return PART_SWAP;
248    }
249    return PART_NONE;
250}
251
252/* If the user wants a special newfs command for this, set it */
253static void
254getNewfsCmd(PartInfo *p)
255{
256    char *val;
257
258    val = msgGetInput(p->newfs_cmd,
259		      "Please enter the newfs command and options you'd like to use in\ncreating this file system.");
260    if (val)
261	strncpy(p->newfs_cmd, val, NEWFS_CMD_MAX);
262}
263
264
265#define MAX_MOUNT_NAME	12
266
267#define PART_PART_COL	0
268#define PART_MOUNT_COL	8
269#define PART_SIZE_COL	(PART_MOUNT_COL + MAX_MOUNT_NAME + 3)
270#define PART_NEWFS_COL	(PART_SIZE_COL + 7)
271#define PART_OFF	38
272
273/* How many mounted partitions to display in column before going to next */
274#define CHUNK_COLUMN_MAX	5
275
276/* stick this all up on the screen */
277static void
278print_label_chunks(void)
279{
280    int i, j, srow, prow, pcol;
281    int sz;
282
283    clear();
284    attrset(A_REVERSE);
285    mvaddstr(0, 25, "FreeBSD Disklabel Editor");
286    attrset(A_NORMAL);
287
288    for (i = 0; i < 2; i++) {
289	mvaddstr(CHUNK_PART_START_ROW - 2, PART_PART_COL + (i * PART_OFF), "Part");
290	mvaddstr(CHUNK_PART_START_ROW - 1, PART_PART_COL + (i * PART_OFF), "----");
291
292	mvaddstr(CHUNK_PART_START_ROW - 2, PART_MOUNT_COL + (i * PART_OFF), "Mount");
293	mvaddstr(CHUNK_PART_START_ROW - 1, PART_MOUNT_COL + (i * PART_OFF), "-----");
294
295	mvaddstr(CHUNK_PART_START_ROW - 2, PART_SIZE_COL + (i * PART_OFF) + 2, "Size");
296	mvaddstr(CHUNK_PART_START_ROW - 1, PART_SIZE_COL + (i * PART_OFF) + 2, "----");
297
298	mvaddstr(CHUNK_PART_START_ROW - 2, PART_NEWFS_COL + (i * PART_OFF), "Newfs");
299	mvaddstr(CHUNK_PART_START_ROW - 1, PART_NEWFS_COL + (i * PART_OFF), "-----");
300    }
301    srow = CHUNK_SLICE_START_ROW;
302    prow = CHUNK_PART_START_ROW;
303    pcol = 0;
304
305    for (i = 0; label_chunk_info[i].d; i++) {
306	if (i == here)
307	    attrset(A_REVERSE);
308	/* Is it a slice entry displayed at the top? */
309	if (label_chunk_info[i].type == PART_SLICE) {
310	    sz = space_free(label_chunk_info[i].c);
311	    mvprintw(srow++, 0,
312		     "Disk: %s\tPartition name: %s\tFree: %d blocks (%dMB)",
313		     label_chunk_info[i].d->name,
314		     label_chunk_info[i].c->name, sz, (sz / 2048));
315	}
316	/* Otherwise it's a DOS, swap or filesystem entry, at the bottom */
317	else {
318	    char onestr[PART_OFF], num[10], *mountpoint, *newfs;
319
320	    /*
321	     * We copy this into a blank-padded string so that it looks like
322	     * a solid bar in reverse-video
323	     */
324	    memset(onestr, ' ', PART_OFF - 1);
325	    onestr[PART_OFF - 1] = '\0';
326	    /* Go for two columns */
327	    if (prow == (CHUNK_PART_START_ROW + CHUNK_COLUMN_MAX)) {
328		pcol = PART_OFF;
329		prow = CHUNK_PART_START_ROW;
330	    }
331	    memcpy(onestr + PART_PART_COL, label_chunk_info[i].c->name,
332		   strlen(label_chunk_info[i].c->name));
333	    /* If it's a filesystem, display the mountpoint */
334	    if (label_chunk_info[i].type == PART_FILESYSTEM) {
335		if (label_chunk_info[i].c->private == NULL) {
336		    static int mnt = 0;
337		    char foo[10];
338
339		    /*
340		     * Hmm!  A partition that must have already been here.
341		     * Fill in a fake mountpoint and register it
342		     */
343		    sprintf(foo, "/mnt%d", mnt++);
344		    label_chunk_info[i].c->private = new_part(foo, FALSE);
345		    label_chunk_info[i].c->private_free = safe_free;
346		}
347		mountpoint = ((PartInfo *)label_chunk_info[i].c->private)->mountpoint;
348		newfs = ((PartInfo *)label_chunk_info[i].c->private)->newfs ? "Y" : "N";
349	    }
350	    else if (label_chunk_info[i].type == PART_SWAP) {
351		mountpoint = "swap";
352		newfs = " ";
353	    }
354	    else if (label_chunk_info[i].type == PART_FAT) {
355		mountpoint = "DOS FAT";
356		newfs = "*";
357	    }
358	    else {
359		mountpoint = "<unknown>";
360		newfs = "*";
361	    }
362	    for (j = 0; j < MAX_MOUNT_NAME && mountpoint[j]; j++)
363		onestr[PART_MOUNT_COL + j] = mountpoint[j];
364	    snprintf(num, 10, "%4ldMB", label_chunk_info[i].c->size ?
365		    label_chunk_info[i].c->size / 2048 : 0);
366	    memcpy(onestr + PART_SIZE_COL, num, strlen(num));
367	    memcpy(onestr + PART_NEWFS_COL, newfs, strlen(newfs));
368	    onestr[PART_NEWFS_COL + strlen(newfs)] = '\0';
369	    mvaddstr(prow, pcol, onestr);
370	    ++prow;
371	}
372	if (i == here)
373	    attrset(A_NORMAL);
374    }
375}
376
377static void
378print_command_summary()
379{
380    mvprintw(17, 0,
381	     "The following commands are valid here (upper or lower case):");
382    mvprintw(19, 0, "C = Create Partition   D = Delete Partition   M = Mount Partition");
383    mvprintw(20, 0, "N = Newfs Options      T = Toggle Newfs       ESC = Exit this screen");
384    mvprintw(21, 0, "The default target will be displayed in ");
385
386    attrset(A_REVERSE);
387    addstr("reverse video.");
388    attrset(A_NORMAL);
389    mvprintw(22, 0, "Use F1 or ? to get more help, arrow keys to move.");
390    move(0, 0);
391}
392
393int
394diskLabelEditor(char *str)
395{
396    int sz, key = 0;
397    Boolean labeling;
398    char *msg = NULL;
399    PartInfo *p;
400    PartType type;
401
402    labeling = TRUE;
403    keypad(stdscr, TRUE);
404    record_label_chunks();
405
406    if (!getenv(DISK_PARTITIONED)) {
407	msgConfirm("You need to partition your disk(s) before you can assign disk labels.");
408	return 0;
409    }
410    while (labeling) {
411	print_label_chunks();
412	print_command_summary();
413	if (msg) {
414	    attrset(A_REVERSE); mvprintw(23, 0, msg); attrset(A_NORMAL);
415	    beep();
416	    msg = NULL;
417	}
418	refresh();
419	key = toupper(getch());
420	switch (key) {
421
422	case KEY_UP:
423	case '-':
424	    if (here != 0)
425		--here;
426	    break;
427
428	case KEY_DOWN:
429	case '+':
430	case '\r':
431	case '\n':
432	    if (label_chunk_info[here + 1].d)
433		++here;
434	    break;
435
436	case KEY_HOME:
437	    here = 0;
438	    break;
439
440	case KEY_END:
441	    while (label_chunk_info[here + 1].d)
442		++here;
443	    break;
444
445	case KEY_F(1):
446	case '?':
447	    systemDisplayFile("disklabel.hlp");
448	    break;
449
450	case 'C':
451	    if (label_chunk_info[here].type != PART_SLICE) {
452		msg = "You can only do this in a master partition (see top of screen)";
453		break;
454	    }
455	    sz = space_free(label_chunk_info[here].c);
456	    if (sz <= FS_MIN_SIZE)
457		msg = "Not enough space to create additional FreeBSD partition";
458	    else {
459		char *val, *cp, tmp[20];
460		int size;
461
462		snprintf(tmp, 20, "%d", sz);
463		val = msgGetInput(tmp, "Please specify the size for new FreeBSD partition in blocks, or append\na trailing `M' for megabytes (e.g. 20M).");
464		if (val && (size = strtol(val, &cp, 0)) > 0) {
465		    struct chunk *tmp;
466		    u_long flags = 0;
467
468		    if (*cp && toupper(*cp) == 'M')
469			size *= 2048;
470
471		    type = get_partition_type();
472		    if (type == PART_NONE)
473			break;
474		    else if (type == PART_FILESYSTEM) {
475			if ((p = get_mountpoint(NULL)) == NULL)
476			    break;
477			else if (!strcmp(p->mountpoint, "/"))
478			    flags |= CHUNK_IS_ROOT;
479			else
480			    flags &= ~CHUNK_IS_ROOT;
481		    }
482		    else
483			p = NULL;
484
485		    if ((flags & CHUNK_IS_ROOT) && !(label_chunk_info[here].c->flags & CHUNK_BSD_COMPAT)) {
486			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!");
487			break;
488		    }
489		    tmp = Create_Chunk_DWIM(label_chunk_info[here].d,
490					    label_chunk_info[here].c,
491					    size, part,
492					    (type == PART_SWAP) ? FS_SWAP : FS_BSDFFS,
493					    flags);
494		    if (!tmp) {
495			msgConfirm("Unable to create the partition. Too big?");
496			break;
497		    }
498		    else if ((flags & CHUNK_IS_ROOT) && (tmp->flags & CHUNK_PAST_1024)) {
499			    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!");
500			    Delete_Chunk(label_chunk_info[here].d, tmp);
501			    break;
502		    }
503		    tmp->private = p;
504		    tmp->private_free = safe_free;
505		    record_label_chunks();
506		}
507	    }
508	    break;
509
510	case 'D':	/* delete */
511	    if (label_chunk_info[here].type == PART_SLICE) {
512		msg = MSG_NOT_APPLICABLE;
513		break;
514	    }
515	    else if (label_chunk_info[here].type == PART_FAT) {
516		msg = "Use the Disk Partition Editor to delete this";
517		break;
518	    }
519	    Delete_Chunk(label_chunk_info[here].d, label_chunk_info[here].c);
520	    record_label_chunks();
521	    break;
522
523	case 'M':	/* mount */
524	    switch(label_chunk_info[here].type) {
525	    case PART_SLICE:
526		msg = MSG_NOT_APPLICABLE;
527		break;
528
529	    case PART_SWAP:
530		msg = "You don't need to specify a mountpoint for a swap partition.";
531		break;
532
533	    case PART_FAT:
534	    case PART_FILESYSTEM:
535		p = get_mountpoint(label_chunk_info[here].c);
536		if (p) {
537		    p->newfs = FALSE;
538		    record_label_chunks();
539		}
540		break;
541
542	    default:
543		msgFatal("Bogus partition under cursor???");
544		break;
545	    }
546	    break;
547
548	case 'N':	/* Set newfs options */
549	    if (label_chunk_info[here].c->private &&
550		((PartInfo *)label_chunk_info[here].c->private)->newfs)
551		getNewfsCmd(label_chunk_info[here].c->private);
552	    else
553		msg = MSG_NOT_APPLICABLE;
554	    break;
555
556	case 'T':	/* Toggle newfs state */
557	    if (label_chunk_info[here].type == PART_FILESYSTEM &&
558		label_chunk_info[here].c->private)
559		((PartInfo *)label_chunk_info[here].c->private)->newfs =
560		    !((PartInfo *)label_chunk_info[here].c->private)->newfs;
561	    else
562		msg = MSG_NOT_APPLICABLE;
563	    break;
564
565	case 'W':
566	    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!")) {
567		int i;
568		Device **devs;
569
570		dialog_clear();
571		end_dialog();
572		DialogActive = FALSE;
573		devs = deviceFind(NULL, DEVICE_TYPE_DISK);
574		if (!devs) {
575		    msgConfirm("Can't find any disk devicse!");
576		    break;
577		}
578		for (i = 0; ((Disk *)devs[i]->private); i++) {
579		    if (devs[i]->enabled)
580		    	slice_wizard(((Disk *)devs[i]->private));
581		}
582		dialog_clear();
583		DialogActive = TRUE;
584		record_label_chunks();
585	    }
586	    else
587		msg = "A most prudent choice!";
588	    break;
589
590	case 27:	/* ESC */
591	    labeling = FALSE;
592	    break;
593
594	default:
595	    beep();
596	    msg = "Type F1 or ? for help";
597	    break;
598	}
599    }
600    variable_set2(DISK_LABELLED, "yes");
601    dialog_clear();
602    refresh();
603    return 0;
604}
605
606
607
608