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