label.c revision 8764
1109998Smarkm/*
2109998Smarkm * The new sysinstall program.
3109998Smarkm *
4109998Smarkm * This is probably the last program in the `sysinstall' line - the next
5109998Smarkm * generation being essentially a complete rewrite.
6109998Smarkm *
7109998Smarkm * $Id: label.c,v 1.25 1995/05/25 18:48:26 jkh Exp $
8109998Smarkm *
9109998Smarkm * Copyright (c) 1995
10215697Ssimon *	Jordan Hubbard.  All rights reserved.
11215697Ssimon *
12109998Smarkm * Redistribution and use in source and binary forms, with or without
13109998Smarkm * modification, are permitted provided that the following conditions
14109998Smarkm * are met:
15109998Smarkm * 1. Redistributions of source code must retain the above copyright
16109998Smarkm *    notice, this list of conditions and the following disclaimer,
17109998Smarkm *    verbatim and that no modifications are made prior to this
18109998Smarkm *    point in the file.
19109998Smarkm * 2. Redistributions in binary form must reproduce the above copyright
20109998Smarkm *    notice, this list of conditions and the following disclaimer in the
21109998Smarkm *    documentation and/or other materials provided with the distribution.
22109998Smarkm * 3. All advertising materials mentioning features or use of this software
23109998Smarkm *    must display the following acknowledgement:
24109998Smarkm *	This product includes software developed by Jordan Hubbard
25109998Smarkm *	for the FreeBSD Project.
26109998Smarkm * 4. The name of Jordan Hubbard or the FreeBSD project may not be used to
27109998Smarkm *    endorse or promote products derived from this software without specific
28109998Smarkm *    prior written permission.
29109998Smarkm *
30109998Smarkm * THIS SOFTWARE IS PROVIDED BY JORDAN HUBBARD ``AS IS'' AND
31109998Smarkm * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
32109998Smarkm * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33109998Smarkm * ARE DISCLAIMED.  IN NO EVENT SHALL JORDAN HUBBARD OR HIS PETS BE LIABLE
34109998Smarkm * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
35109998Smarkm * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
36109998Smarkm * OR SERVICES; LOSS OF USE, DATA, LIFE OR PROFITS; OR BUSINESS INTERRUPTION)
37109998Smarkm * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
38109998Smarkm * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
39109998Smarkm * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
40109998Smarkm * SUCH DAMAGE.
41109998Smarkm *
42109998Smarkm */
43109998Smarkm
44109998Smarkm#include "sysinstall.h"
45109998Smarkm#include <ctype.h>
46109998Smarkm#include <sys/disklabel.h>
47109998Smarkm
48109998Smarkm/*
49109998Smarkm * Everything to do with editing the contents of disk labels.
50109998Smarkm */
51109998Smarkm
52109998Smarkm/* A nice message we use a lot in the disklabel editor */
53109998Smarkm#define MSG_NOT_APPLICABLE	"That option is not applicable here"
54109998Smarkm
55109998Smarkm/* Where to start printing the freebsd slices */
56109998Smarkm#define CHUNK_SLICE_START_ROW		2
57109998Smarkm#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
83	    && label_chunk_info[i].c->private
84	    && !strcmp(((PartInfo *)label_chunk_info[i].c->private)->mountpoint, name))
85	    return TRUE;
86    return FALSE;
87}
88
89/* How much space is in this FreeBSD slice? */
90static int
91space_free(struct chunk *c)
92{
93    struct chunk *c1 = c->part;
94    int sz = c->size;
95
96    while (c1) {
97	if (c1->type != unused)
98	    sz -= c1->size;
99	c1 = c1->next;
100    }
101    if (sz < 0)
102	msgFatal("Partitions are larger than actual chunk??");
103    return sz;
104}
105
106/* Snapshot the current situation into the displayed chunks structure */
107static void
108record_label_chunks()
109{
110    int i, j, p;
111    struct chunk *c1, *c2;
112    Device **devs;
113    Disk *d;
114
115    devs = deviceFind(NULL, DEVICE_TYPE_DISK);
116    if (!devs) {
117	msgConfirm("No disks found!");
118	return;
119    }
120
121    j = p = 0;
122    /* First buzz through and pick up the FreeBSD slices */
123    for (i = 0; devs[i]; i++) {
124	if (!devs[i]->enabled)
125	    continue;
126	d = (Disk *)devs[i]->private;
127	if (!d->chunks)
128	    msgFatal("No chunk list found for %s!", d->name);
129
130	/* Put the slice entries first */
131	for (c1 = d->chunks->part; c1; c1 = c1->next) {
132	    if (c1->type == freebsd) {
133		label_chunk_info[j].type = PART_SLICE;
134		label_chunk_info[j].c = c1;
135		++j;
136	    }
137	}
138    }
139    /* Now run through again and get the FreeBSD partition entries */
140    for (i = 0; devs[i]; i++) {
141	if (!devs[i]->enabled)
142	    continue;
143	d = (Disk *)devs[i]->private;
144	/* Then buzz through and pick up the partitions */
145	for (c1 = d->chunks->part; c1; c1 = c1->next) {
146	    if (c1->type == freebsd) {
147		for (c2 = c1->part; c2; c2 = c2->next) {
148		    if (c2->type == part) {
149			if (c2->subtype == FS_SWAP)
150			    label_chunk_info[j].type = PART_SWAP;
151			else
152			    label_chunk_info[j].type = PART_FILESYSTEM;
153			label_chunk_info[j].c = c2;
154			++j;
155		    }
156		}
157	    }
158	    else if (c1->type == fat) {
159		label_chunk_info[j].type = PART_FAT;
160		label_chunk_info[j].c = c1;
161		++j;
162	    }
163	}
164    }
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, u_long size)
173{
174    PartInfo *ret;
175    u_long target,divisor;
176
177    ret = (PartInfo *)safe_malloc(sizeof(PartInfo));
178    strncpy(ret->mountpoint, mpoint, FILENAME_MAX);
179    strcpy(ret->newfs_cmd, "newfs");
180    ret->newfs = newfs;
181    if (!size)
182	    return ret;
183    for(target = size; target; target--) {
184	for(divisor = 4096 ; divisor > 1023; divisor--) {
185	    if (!(target % divisor)) {
186		sprintf(ret->newfs_cmd+strlen(ret->newfs_cmd),
187		    " -u %ld",divisor);
188		return ret;
189	    }
190	}
191    }
192    return ret;
193}
194
195/* Get the mountpoint for a partition and save it away */
196PartInfo *
197get_mountpoint(struct chunk *old)
198{
199    char *val;
200    PartInfo *tmp;
201
202    val = msgGetInput(old && old->private ? ((PartInfo *)old->private)->mountpoint : NULL,
203		      "Please specify a mount point for the partition");
204    if (!val || !*val) {
205	if (!old)
206	    return NULL;
207	else {
208	    free(old->private);
209	    old->private = NULL;
210	}
211	return NULL;
212    }
213
214    /* Is it just the same value? */
215    if (old && old->private && !strcmp(((PartInfo *)old->private)->mountpoint, val))
216	return NULL;
217    if (check_conflict(val)) {
218	msgConfirm("You already have a mount point for %s assigned!", val);
219	return NULL;
220    }
221    if (*val != '/') {
222	msgConfirm("Mount point must start with a / character");
223	return NULL;
224    }
225    if (!strcmp(val, "/")) {
226	if (old)
227	    old->flags |= CHUNK_IS_ROOT;
228    } else if (old) {
229	old->flags &= ~CHUNK_IS_ROOT;
230    }
231    safe_free(old ? old->private : NULL);
232    tmp = new_part(val, TRUE, 0);
233    if (old) {
234	old->private = tmp;
235	old->private_free = safe_free;
236    }
237    return tmp;
238}
239
240/* Get the type of the new partiton */
241static PartType
242get_partition_type(void)
243{
244    char selection[20];
245    int i;
246
247    static unsigned char *fs_types[] = {
248	"FS",
249	"A file system",
250	"Swap",
251	"A swap partition.",
252    };
253    i = dialog_menu("Please choose a partition type",
254		    "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);
255    if (!i) {
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	5
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    attrset(A_REVERSE);
296    mvaddstr(0, 25, "FreeBSD Disklabel Editor");
297    clrtobot();
298    attrset(A_NORMAL);
299
300    for (i = 0; i < 2; i++) {
301	mvaddstr(CHUNK_PART_START_ROW - 2, PART_PART_COL + (i * PART_OFF), "Part");
302	mvaddstr(CHUNK_PART_START_ROW - 1, PART_PART_COL + (i * PART_OFF), "----");
303
304	mvaddstr(CHUNK_PART_START_ROW - 2, PART_MOUNT_COL + (i * PART_OFF), "Mount");
305	mvaddstr(CHUNK_PART_START_ROW - 1, PART_MOUNT_COL + (i * PART_OFF), "-----");
306
307	mvaddstr(CHUNK_PART_START_ROW - 2, PART_SIZE_COL + (i * PART_OFF) + 2, "Size");
308	mvaddstr(CHUNK_PART_START_ROW - 1, PART_SIZE_COL + (i * PART_OFF) + 2, "----");
309
310	mvaddstr(CHUNK_PART_START_ROW - 2, PART_NEWFS_COL + (i * PART_OFF), "Newfs");
311	mvaddstr(CHUNK_PART_START_ROW - 1, PART_NEWFS_COL + (i * PART_OFF), "-----");
312    }
313    srow = CHUNK_SLICE_START_ROW;
314    prow = CHUNK_PART_START_ROW;
315    pcol = 0;
316
317    for (i = 0; label_chunk_info[i].c; i++) {
318	if (i == here)
319	    attrset(A_REVERSE);
320	/* Is it a slice entry displayed at the top? */
321	if (label_chunk_info[i].type == PART_SLICE) {
322	    sz = space_free(label_chunk_info[i].c);
323	    mvprintw(srow++, 0, "Disk: %s\tPartition name: %s\tFree: %d blocks (%dMB)",
324		     label_chunk_info[i].c->disk->name, label_chunk_info[i].c->name, sz, (sz / ONE_MEG));
325	}
326	/* Otherwise it's a DOS, swap or filesystem entry, at the bottom */
327	else {
328	    char onestr[PART_OFF], num[10], *mountpoint, *newfs;
329
330	    /*
331	     * We copy this into a blank-padded string so that it looks like
332	     * a solid bar in reverse-video
333	     */
334	    memset(onestr, ' ', PART_OFF - 1);
335	    onestr[PART_OFF - 1] = '\0';
336	    /* Go for two columns */
337	    if (prow == (CHUNK_PART_START_ROW + CHUNK_COLUMN_MAX)) {
338		pcol = PART_OFF;
339		prow = CHUNK_PART_START_ROW;
340	    }
341	    memcpy(onestr + PART_PART_COL, label_chunk_info[i].c->name, 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 || label_chunk_info[i].type == PART_FAT) {
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, label_chunk_info[i].c->size);
354		    label_chunk_info[i].c->private_free = safe_free;
355		}
356		mountpoint = ((PartInfo *)label_chunk_info[i].c->private)->mountpoint;
357		if (label_chunk_info[i].type == PART_FAT)
358		    newfs = "DOS";
359		else
360		    newfs = ((PartInfo *)label_chunk_info[i].c->private)->newfs ? "Y" : "N";
361	    }
362	    else if (label_chunk_info[i].type == PART_SWAP) {
363		mountpoint = "swap";
364		newfs = " ";
365	    }
366	    else {
367		mountpoint = "<NONE>";
368		newfs = "*";
369	    }
370	    for (j = 0; j < MAX_MOUNT_NAME && mountpoint[j]; j++)
371		onestr[PART_MOUNT_COL + j] = mountpoint[j];
372	    snprintf(num, 10, "%4ldMB", label_chunk_info[i].c->size ? label_chunk_info[i].c->size / ONE_MEG : 0);
373	    memcpy(onestr + PART_SIZE_COL, num, strlen(num));
374	    memcpy(onestr + PART_NEWFS_COL, newfs, strlen(newfs));
375	    onestr[PART_NEWFS_COL + strlen(newfs)] = '\0';
376	    mvaddstr(prow, pcol, onestr);
377	    ++prow;
378	}
379	if (i == here)
380	    attrset(A_NORMAL);
381    }
382}
383
384static void
385print_command_summary()
386{
387    mvprintw(17, 0,
388	     "The following commands are valid here (upper or lower case):");
389    mvprintw(19, 0, "C = Create Partition   D = Delete Partition   M = Mount Partition");
390    mvprintw(20, 0, "N = Newfs Options      T = Toggle Newfs       ESC = Exit this screen");
391    mvprintw(21, 0, "The default target will be displayed in ");
392
393    attrset(A_REVERSE);
394    addstr("reverse");
395    attrset(A_NORMAL);
396    addstr(" video.");
397    mvprintw(22, 0, "Use F1 or ? to get more help, arrow keys to move.");
398    move(0, 0);
399}
400
401int
402diskLabelEditor(char *str)
403{
404    int sz, key = 0;
405    Boolean labeling;
406    char *msg = NULL;
407    PartInfo *p;
408    PartType type;
409
410    labeling = TRUE;
411    keypad(stdscr, TRUE);
412    record_label_chunks();
413
414    if (!getenv(DISK_PARTITIONED)) {
415	msgConfirm("You need to partition your disk(s) before you can assign disk labels.");
416	return 0;
417    }
418    dialog_clear(); clear();
419    while (labeling) {
420	clear();
421	print_label_chunks();
422	print_command_summary();
423	if (msg) {
424	    attrset(A_REVERSE); mvprintw(23, 0, msg); attrset(A_NORMAL);
425	    beep();
426	    msg = NULL;
427	}
428	refresh();
429	key = toupper(getch());
430	switch (key) {
431
432	case '\014':	/* ^L */
433	    continue;
434
435	case KEY_UP:
436	case '-':
437	    if (here != 0)
438		--here;
439	    else
440		while (label_chunk_info[here + 1].c)
441		    ++here;
442	    break;
443
444	case KEY_DOWN:
445	case '+':
446	case '\r':
447	case '\n':
448	    if (label_chunk_info[here + 1].c)
449		++here;
450	    else
451		here = 0;
452	    break;
453
454	case KEY_HOME:
455	    here = 0;
456	    break;
457
458	case KEY_END:
459	    while (label_chunk_info[here + 1].c)
460		++here;
461	    break;
462
463	case KEY_F(1):
464	case '?':
465	    systemDisplayFile("disklabel.hlp");
466	    break;
467
468	case 'C':
469	    if (label_chunk_info[here].type != PART_SLICE) {
470		msg = "You can only do this in a master partition (see top of screen)";
471		break;
472	    }
473	    sz = space_free(label_chunk_info[here].c);
474	    if (sz <= FS_MIN_SIZE) {
475		msg = "Not enough space to create additional FreeBSD partition";
476		break;
477	    }
478	    {
479		char *val, *cp;
480		int size;
481		struct chunk *tmp;
482		u_long flags = 0;
483
484		val = msgGetInput(NULL, "Please specify the size for new FreeBSD partition in blocks, or\nappend a trailing `M' for megabytes (e.g. 20M), `C' for cylinders\nor `%%' for a percentage of remaining space.\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 (sz <= FS_MIN_SIZE) {
489		    msgConfirm("The minimum filesystem size is %dMB", FS_MIN_SIZE / ONE_MEG);
490		    break;
491		}
492		if (*cp) {
493		    if (toupper(*cp) == 'M')
494			size *= ONE_MEG;
495		    else if (toupper(*cp) == 'C')
496			size *= (label_chunk_info[here].c->disk->bios_hd * label_chunk_info[here].c->disk->bios_sect);
497		    else if (*cp == '%') {
498			float fsz, fsize;
499
500			fsz = (float)sz;
501			fsize = (float)size;
502			fsize *= 0.10;
503			fsz *= fsize;
504			size = (int)fsz;
505		    }
506		}
507		type = get_partition_type();
508		if (type == PART_NONE)
509		    break;
510
511		if (type == PART_FILESYSTEM) {
512		    if ((p = get_mountpoint(NULL)) == NULL)
513			break;
514		    else if (!strcmp(p->mountpoint, "/"))
515			flags |= CHUNK_IS_ROOT;
516		    else
517			flags &= ~CHUNK_IS_ROOT;
518		} else
519		    p = NULL;
520
521		if ((flags & CHUNK_IS_ROOT)) {
522		    if (!(label_chunk_info[here].c->flags & CHUNK_BSD_COMPAT)) {
523			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!");
524			break;
525		    }
526		    if (size < ROOT_MIN_SIZE)
527			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);
528		}
529		tmp = Create_Chunk_DWIM(label_chunk_info[here].c->disk,
530					label_chunk_info[here].c,
531					size, part,
532					(type == PART_SWAP) ? FS_SWAP : FS_BSDFFS,
533					flags);
534		if (!tmp) {
535		    msgConfirm("Unable to create the partition. Too big?");
536		    break;
537		}
538		if ((flags & CHUNK_IS_ROOT) && (tmp->flags & CHUNK_PAST_1024)) {
539		    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!");
540		    Delete_Chunk(label_chunk_info[here].c->disk, tmp);
541		    break;
542		}
543		if (type != PART_SWAP) {
544		    /* This is needed to tell the newfs -u about the size */
545		    tmp->private = new_part(p->mountpoint,p->newfs,tmp->size);
546		    safe_free(p);
547		} else {
548		    tmp->private = p;
549		}
550		tmp->private_free = safe_free;
551		record_label_chunks();
552	    }
553	    break;
554
555	case 'D':	/* delete */
556	    if (label_chunk_info[here].type == PART_SLICE) {
557		msg = MSG_NOT_APPLICABLE;
558		break;
559	    }
560	    else if (label_chunk_info[here].type == PART_FAT) {
561		msg = "Use the Disk Partition Editor to delete DOS partitions";
562		break;
563	    }
564	    Delete_Chunk(label_chunk_info[here].c->disk, label_chunk_info[here].c);
565	    record_label_chunks();
566	    break;
567
568	case 'M':	/* mount */
569	    switch(label_chunk_info[here].type) {
570	    case PART_SLICE:
571		msg = MSG_NOT_APPLICABLE;
572		break;
573
574	    case PART_SWAP:
575		msg = "You don't need to specify a mountpoint for a swap partition.";
576		break;
577
578	    case PART_FAT:
579	    case PART_FILESYSTEM:
580		p = get_mountpoint(label_chunk_info[here].c);
581		if (p) {
582		    p->newfs = FALSE;
583		    if (label_chunk_info[here].type == PART_FAT
584			&& (!strcmp(p->mountpoint, "/") || !strcmp(p->mountpoint, "/usr")
585			    || !strcmp(p->mountpoint, "/var"))) {
586			msgConfirm("%s is an invalid mount point for a DOS partition!", p->mountpoint);
587			strcpy(p->mountpoint, "/bogus");
588		    }
589		    record_label_chunks();
590		}
591		break;
592
593	    default:
594		msgFatal("Bogus partition under cursor???");
595		break;
596	    }
597	    break;
598
599	case 'N':	/* Set newfs options */
600	    if (label_chunk_info[here].c->private &&
601		((PartInfo *)label_chunk_info[here].c->private)->newfs)
602		getNewfsCmd(label_chunk_info[here].c->private);
603	    else
604		msg = MSG_NOT_APPLICABLE;
605	    break;
606
607	case 'T':	/* Toggle newfs state */
608	    if (label_chunk_info[here].type == PART_FILESYSTEM &&
609		label_chunk_info[here].c->private) {
610		    PartInfo *pi = ((PartInfo *)
611			label_chunk_info[here].c->private);
612		    label_chunk_info[here].c->private = new_part(
613			pi->mountpoint,
614			!pi->newfs,
615			label_chunk_info[here].c->size);
616		    safe_free(pi);
617		}
618	    else
619		msg = MSG_NOT_APPLICABLE;
620	    break;
621
622	case 'W':
623	    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!")) {
624		int i;
625		Device **devs;
626
627		dialog_clear();
628		end_dialog();
629		DialogActive = FALSE;
630		devs = deviceFind(NULL, DEVICE_TYPE_DISK);
631		if (!devs) {
632		    msgConfirm("Can't find any disk devicse!");
633		    break;
634		}
635		for (i = 0; devs[i] && ((Disk *)devs[i]->private); i++) {
636		    if (devs[i]->enabled)
637		    	slice_wizard(((Disk *)devs[i]->private));
638		}
639		DialogActive = TRUE;
640		dialog_clear();
641		record_label_chunks();
642	    }
643	    else
644		msg = "A most prudent choice!";
645	    break;
646
647	case 27:	/* ESC */
648	    labeling = FALSE;
649	    break;
650
651	default:
652	    beep();
653	    msg = "Type F1 or ? for help";
654	    break;
655	}
656    }
657    variable_set2(DISK_LABELLED, "yes");
658    dialog_clear();
659    return 0;
660}
661
662
663
664