label.c revision 8577
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.3 1995/05/17 14:39:45 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		10
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 *parent, struct chunk *me)
195{
196    char *val;
197    PartInfo *tmp;
198
199    val = msgGetInput(me && me->private ? ((PartInfo *)me->private)->mountpoint : NULL,
200		      "Please specify a mount point for the partition");
201    if (val) {
202	/* Is it just the same value? */
203	if (me && me->private && !strcmp(((PartInfo *)me->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 0
215	    if (parent) {
216	    	if (parent->flags & CHUNK_PAST_1024) {
217		    msgConfirm("This region cannot be used for your root partition as\nit is past the 1024'th cylinder mark and the system would not be\nable to boot from it.  Please pick another location for your\nroot partition and try again!");
218		    return NULL;
219		}
220		else if (!(parent->flags & CHUNK_BSD_COMPAT)) {
221		    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 region.  Please choose another partition for this.");
222		    return NULL;
223		}
224	    }
225#endif
226	    if (me)
227		me->flags |= CHUNK_IS_ROOT;
228	}
229	else if (me)
230	    me->flags &= ~CHUNK_IS_ROOT;
231	safe_free(me ? me->private : NULL);
232	tmp = new_part(val, TRUE);
233	if (me) {
234	    me->private = tmp;
235	    me->private_free = safe_free;
236	}
237	return tmp;
238    }
239    return NULL;
240}
241
242/* Get the type of the new partiton */
243static PartType
244get_partition_type(void)
245{
246    char selection[20];
247    static unsigned char *fs_types[] = {
248	"FS",
249	"A file system",
250	"Swap",
251	"A swap partition.",
252    };
253
254    if (!dialog_menu("Please choose a partition type",
255		    "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)) {
256	if (!strcmp(selection, "FS"))
257	    return PART_FILESYSTEM;
258	else if (!strcmp(selection, "Swap"))
259	    return PART_SWAP;
260    }
261    return PART_NONE;
262}
263
264/* If the user wants a special newfs command for this, set it */
265static void
266getNewfsCmd(PartInfo *p)
267{
268    char *val;
269
270    val = msgGetInput(p->newfs_cmd,
271		      "Please enter the newfs command and options you'd like to use in\ncreating this file system.");
272    if (val)
273	strncpy(p->newfs_cmd, val, NEWFS_CMD_MAX);
274}
275
276
277#define MAX_MOUNT_NAME	12
278
279#define PART_PART_COL	0
280#define PART_MOUNT_COL	8
281#define PART_SIZE_COL	(PART_MOUNT_COL + MAX_MOUNT_NAME + 3)
282#define PART_NEWFS_COL	(PART_SIZE_COL + 7)
283#define PART_OFF	38
284
285/* How many mounted partitions to display in column before going to next */
286#define CHUNK_COLUMN_MAX	6
287
288/* stick this all up on the screen */
289static void
290print_label_chunks(void)
291{
292    int i, j, srow, prow, pcol;
293    int sz;
294
295    clear();
296    attrset(A_REVERSE);
297    mvaddstr(0, 25, "FreeBSD Disklabel Editor");
298    attrset(A_NORMAL);
299
300    for (i = 0; i < 2; i++) {
301	attrset(A_UNDERLINE);
302	mvaddstr(CHUNK_PART_START_ROW - 1, PART_PART_COL + (i * PART_OFF),
303		 "Part");
304	attrset(A_NORMAL);
305
306	attrset(A_UNDERLINE);
307	mvaddstr(CHUNK_PART_START_ROW - 1, PART_MOUNT_COL + (i * PART_OFF),
308		 "Mount");
309	attrset(A_NORMAL);
310
311	attrset(A_UNDERLINE);
312	mvaddstr(CHUNK_PART_START_ROW - 1, PART_SIZE_COL + (i * PART_OFF) + 2,
313		 "Size");
314	attrset(A_NORMAL);
315
316	attrset(A_UNDERLINE);
317	mvaddstr(CHUNK_PART_START_ROW - 1, PART_NEWFS_COL + (i * PART_OFF),
318		 "Newfs");
319	attrset(A_NORMAL);
320    }
321
322    srow = CHUNK_SLICE_START_ROW;
323    prow = CHUNK_PART_START_ROW;
324    pcol = 0;
325
326    for (i = 0; label_chunk_info[i].d; i++) {
327	if (i == here)
328	    attrset(A_REVERSE);
329	/* Is it a slice entry displayed at the top? */
330	if (label_chunk_info[i].type == PART_SLICE) {
331	    sz = space_free(label_chunk_info[i].c);
332	    mvprintw(srow++, 0,
333		     "Disk: %s\tPartition name: %s\tFree: %d blocks (%dMB)",
334		     label_chunk_info[i].d->name,
335		     label_chunk_info[i].c->name, sz, (sz / 2048));
336	}
337	/* Otherwise it's a DOS, swap or filesystem entry, at the bottom */
338	else {
339	    char onestr[PART_OFF], num[10], *mountpoint, *newfs;
340
341	    /*
342	     * We copy this into a blank-padded string so that it looks like
343	     * a solid bar in reverse-video
344	     */
345	    memset(onestr, ' ', PART_OFF - 1);
346	    onestr[PART_OFF - 1] = '\0';
347	    /* Go for two columns */
348	    if (prow == (CHUNK_PART_START_ROW + CHUNK_COLUMN_MAX)) {
349		pcol = PART_OFF;
350		prow = CHUNK_PART_START_ROW;
351	    }
352	    memcpy(onestr + PART_PART_COL, label_chunk_info[i].c->name,
353		   strlen(label_chunk_info[i].c->name));
354	    /* If it's a filesystem, display the mountpoint */
355	    if (label_chunk_info[i].type == PART_FILESYSTEM) {
356		if (label_chunk_info[i].c->private == NULL) {
357		    static int mnt = 0;
358		    char foo[10];
359
360		    /*
361		     * Hmm!  A partition that must have already been here.
362		     * Fill in a fake mountpoint and register it
363		     */
364		    sprintf(foo, "/mnt%d", mnt++);
365		    label_chunk_info[i].c->private = new_part(foo, FALSE);
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 / 2048 : 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    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	    else {
480		char *val, *cp, tmp[20];
481		int size;
482
483		snprintf(tmp, 20, "%d", sz);
484		val = msgGetInput(tmp, "Please specify the size for new FreeBSD partition in blocks, or append\na trailing `M' for megabytes (e.g. 20M).");
485		if (val && (size = strtol(val, &cp, 0)) > 0) {
486		    struct chunk *tmp;
487		    u_long flags = 0;
488
489		    if (*cp && toupper(*cp) == 'M')
490			size *= 2048;
491
492		    type = get_partition_type();
493		    if (type == PART_NONE)
494			break;
495		    else if (type == PART_FILESYSTEM) {
496			if ((p = get_mountpoint(label_chunk_info[here].c, NULL)) == NULL)
497			    break;
498			else if (!strcmp(p->mountpoint, "/"))
499			    flags |= CHUNK_IS_ROOT;
500			else
501			    flags &= ~CHUNK_IS_ROOT;
502		    }
503		    else
504			p = NULL;
505
506		    tmp = Create_Chunk_DWIM(label_chunk_info[here].d,
507					    label_chunk_info[here].c,
508					    size, part,
509					    (type == PART_SWAP) ? FS_SWAP : FS_BSDFFS,
510					    flags);
511		    if (!tmp)
512			msgConfirm("Unable to create the partition. Too big?");
513		    else {
514			tmp->private = p;
515			tmp->private_free = safe_free;
516			record_label_chunks();
517		    }
518		}
519	    }
520	    break;
521
522	case 'D':	/* delete */
523	    if (label_chunk_info[here].type == PART_SLICE) {
524		msg = MSG_NOT_APPLICABLE;
525		break;
526	    }
527	    else if (label_chunk_info[here].type == PART_FAT) {
528		msg = "Use the Disk Partition Editor to delete this";
529		break;
530	    }
531	    Delete_Chunk(label_chunk_info[here].d, label_chunk_info[here].c);
532	    record_label_chunks();
533	    break;
534
535	case 'M':	/* mount */
536	    switch(label_chunk_info[here].type) {
537	    case PART_SLICE:
538		msg = MSG_NOT_APPLICABLE;
539		break;
540
541	    case PART_SWAP:
542		msg = "You don't need to specify a mountpoint for a swap partition.";
543		break;
544
545	    case PART_FAT:
546	    case PART_FILESYSTEM:
547		p = get_mountpoint(NULL, label_chunk_info[here].c);
548		if (p) {
549		    p->newfs = FALSE;
550		    record_label_chunks();
551		}
552		break;
553
554	    default:
555		msgFatal("Bogus partition under cursor???");
556		break;
557	    }
558	    break;
559
560	case 'N':	/* Set newfs options */
561	    if (label_chunk_info[here].c->private &&
562		((PartInfo *)label_chunk_info[here].c->private)->newfs)
563		getNewfsCmd(label_chunk_info[here].c->private);
564	    else
565		msg = MSG_NOT_APPLICABLE;
566	    break;
567
568	case 'T':	/* Toggle newfs state */
569	    if (label_chunk_info[here].type == PART_FILESYSTEM &&
570		label_chunk_info[here].c->private)
571		((PartInfo *)label_chunk_info[here].c->private)->newfs =
572		    !((PartInfo *)label_chunk_info[here].c->private)->newfs;
573	    else
574		msg = MSG_NOT_APPLICABLE;
575	    break;
576
577	case 'W':
578	    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!")) {
579		int i;
580		Device **devs;
581
582		dialog_clear();
583		end_dialog();
584		DialogActive = FALSE;
585		devs = deviceFind(NULL, DEVICE_TYPE_DISK);
586		if (!devs) {
587		    msgConfirm("Can't find any disk devicse!");
588		    break;
589		}
590		for (i = 0; ((Disk *)devs[i]->private); i++)
591		    slice_wizard(((Disk *)devs[i]->private));
592		dialog_clear();
593		DialogActive = TRUE;
594		record_label_chunks();
595	    }
596	    else
597		msg = "A most prudent choice!";
598	    break;
599
600	case 27:	/* ESC */
601	    labeling = FALSE;
602	    break;
603
604	default:
605	    beep();
606	    msg = "Type F1 or ? for help";
607	    break;
608	}
609    }
610    variable_set2(DISK_LABELLED, "yes");
611    clear();
612    refresh();
613    return 0;
614}
615
616
617
618