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