1 /* $NetBSD: md.c,v 1.12 2020/10/14 08:49:04 martin Exp $ */ 2 3 /* 4 * Copyright 1997 Piermont Information Systems Inc. 5 * All rights reserved. 6 * 7 * Based on code written by Philip A. Nelson for Piermont Information 8 * Systems Inc. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 3. The name of Piermont Information Systems Inc. may not be used to endorse 19 * or promote products derived from this software without specific prior 20 * written permission. 21 * 22 * THIS SOFTWARE IS PROVIDED BY PIERMONT INFORMATION SYSTEMS INC. ``AS IS'' 23 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 24 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 25 * ARE DISCLAIMED. IN NO EVENT SHALL PIERMONT INFORMATION SYSTEMS INC. BE 26 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 27 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 28 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 29 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 30 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 31 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 32 * THE POSSIBILITY OF SUCH DAMAGE. 33 */ 34 35 /* md.c -- ofppc machine specific routines */ 36 37 #include <sys/param.h> 38 #include <sys/sysctl.h> 39 #include <sys/disklabel_rdb.h> 40 #include <stdio.h> 41 #include <util.h> 42 #include <machine/cpu.h> 43 44 #include "defs.h" 45 #include "md.h" 46 #include "msg_defs.h" 47 #include "menu_defs.h" 48 #include "endian.h" 49 50 static int check_rdb(void); 51 static uint32_t rdbchksum(void *); 52 53 /* We use MBR_PTYPE_PREP like port-prep does. */ 54 static int nonewfsmsdos = 0, nobootfix = 0, noprepfix=0; 55 static part_id bootpart_fat12 = NO_PART, bootpart_binfo = NO_PART, 56 bootpart_prep = NO_PART; 57 static int bootinfo_mbr = 1; 58 static int rdb_found = 0; 59 60 /* bootstart/bootsize are for the fat */ 61 int binfostart, binfosize, bprepstart, bprepsize; 62 63 void 64 md_init(void) 65 { 66 } 67 68 void 69 md_init_set_status(int flags) 70 { 71 72 (void)flags; 73 } 74 75 bool 76 md_get_info(struct install_partition_desc *install) 77 { 78 int res; 79 80 if (check_rdb()) 81 return true; 82 83 84 if (pm->no_mbr || pm->no_part) 85 return true; 86 87 again: 88 if (pm->parts == NULL) { 89 90 const struct disk_partitioning_scheme *ps = 91 select_part_scheme(pm, NULL, true, NULL); 92 93 if (!ps) 94 return false; 95 96 struct disk_partitions *parts = 97 (*ps->create_new_for_disk)(pm->diskdev, 98 0, pm->dlsize, true, NULL); 99 if (!parts) 100 return false; 101 102 pm->parts = parts; 103 if (ps->size_limit > 0 && pm->dlsize > ps->size_limit) 104 pm->dlsize = ps->size_limit; 105 } 106 107 res = set_bios_geom_with_mbr_guess(pm->parts); 108 if (res == 0) 109 return false; 110 else if (res == 1) 111 return true; 112 113 pm->parts->pscheme->destroy_part_scheme(pm->parts); 114 pm->parts = NULL; 115 goto again; 116 } 117 118 /* 119 * md back-end code for menu-driven BSD disklabel editor. 120 */ 121 int 122 md_make_bsd_partitions(struct install_partition_desc *install) 123 { 124 #if 0 125 int i; 126 int part; 127 int maxpart = getmaxpartitions(); 128 int partstart; 129 int part_raw, part_bsd; 130 int ptend; 131 int no_swap = 0; 132 #endif 133 134 if (rdb_found) { 135 #if 0 136 /* 137 * XXX - need to test on real machine if the disklabel code 138 * deals with RDB partitions properly, otherwise write 139 * a read-only RDB backend 140 */ 141 /* 142 * We found RDB partitions on the disk, which cannot be 143 * modified by rewriting the disklabel. 144 * So just use what we have got. 145 */ 146 for (part = 0; part < maxpart; part++) { 147 if (PI_ISBSDFS(&pm->bsdlabel[part])) { 148 pm->bsdlabel[part].pi_flags |= 149 PIF_NEWFS | PIF_MOUNT; 150 151 if (part == PART_A) 152 strcpy(pm->bsdlabel[part].pi_mount, "/"); 153 } 154 } 155 156 part_bsd = part_raw = getrawpartition(); 157 if (part_raw == -1) 158 part_raw = PART_C; /* for sanity... */ 159 pm->bsdlabel[part_raw].pi_offset = 0; 160 pm->bsdlabel[part_raw].pi_size = pm->dlsize; 161 162 set_sizemultname_meg(); 163 rdb_edit_check: 164 if (edit_and_check_label(pm->bsdlabel, maxpart, part_raw, 165 part_bsd) == 0) { 166 msg_display(MSG_abort); 167 return 0; 168 } 169 if (md_check_partitions() == 0) 170 goto rdb_edit_check; 171 #endif 172 return 1; 173 } 174 175 /* 176 * Initialize global variables that track space used on this disk. 177 * Standard 4.4BSD 8-partition labels always cover whole disk. 178 */ 179 if (pm->ptsize == 0) 180 pm->ptsize = pm->dlsize - pm->ptstart; 181 if (pm->dlsize == 0) 182 pm->dlsize = pm->ptstart + pm->ptsize; 183 184 #if 0 185 partstart = pm->ptstart; 186 ptend = pm->ptstart + pm->ptsize; 187 188 /* Ask for layout type -- standard or special */ 189 msg_fmt_display(MSG_layout, "%d%d%d", 190 pm->ptsize / (MEG / pm->sectorsize), 191 DEFROOTSIZE + DEFSWAPSIZE + DEFUSRSIZE, 192 DEFROOTSIZE + DEFSWAPSIZE + DEFUSRSIZE + XNEEDMB); 193 194 process_menu(MENU_layout, NULL); 195 196 /* Set so we use the 'real' geometry for rounding, input in MB */ 197 pm->current_cylsize = pm->dlcylsize; 198 set_sizemultname_meg(); 199 200 /* Build standard partitions */ 201 memset(&pm->bsdlabel, 0, sizeof pm->bsdlabel); 202 203 /* Set initial partition types to unused */ 204 for (part = 0 ; part < maxpart ; ++part) 205 pm->bsdlabel[part].pi_fstype = FS_UNUSED; 206 207 /* Whole disk partition */ 208 part_raw = getrawpartition(); 209 if (part_raw == -1) 210 part_raw = PART_C; /* for sanity... */ 211 pm->bsdlabel[part_raw].pi_offset = 0; 212 pm->bsdlabel[part_raw].pi_size = pm->dlsize; 213 214 if (part_raw == PART_D) { 215 /* Probably a system that expects an i386 style mbr */ 216 part_bsd = PART_C; 217 pm->bsdlabel[PART_C].pi_offset = pm->ptstart; 218 pm->bsdlabel[PART_C].pi_size = pm->ptsize; 219 } else { 220 part_bsd = part_raw; 221 } 222 223 if (pm->bootsize != 0) { 224 pm->bsdlabel[PART_BOOT_FAT12].pi_fstype = FS_MSDOS; 225 pm->bsdlabel[PART_BOOT_FAT12].pi_size = pm->bootsize; 226 pm->bsdlabel[PART_BOOT_FAT12].pi_offset = pm->bootstart; 227 pm->bsdlabel[PART_BOOT_FAT12].pi_flags |= PART_BOOT_FAT12_PI_FLAGS; 228 strlcpy(pm->bsdlabel[PART_BOOT_FAT12].pi_mount, 229 PART_BOOT_FAT12_PI_MOUNT, 230 sizeof pm->bsdlabel[PART_BOOT_FAT12].pi_mount); 231 } 232 if (binfosize != 0) { 233 pm->bsdlabel[PART_BOOT_BINFO].pi_fstype = FS_OTHER; 234 pm->bsdlabel[PART_BOOT_BINFO].pi_size = binfosize; 235 pm->bsdlabel[PART_BOOT_BINFO].pi_offset = binfostart; 236 } 237 if (bprepsize != 0) { 238 pm->bsdlabel[PART_BOOT_PREP].pi_fstype = FS_BOOT; 239 pm->bsdlabel[PART_BOOT_PREP].pi_size = bprepsize; 240 pm->bsdlabel[PART_BOOT_PREP].pi_offset = bprepstart; 241 } 242 243 #ifdef PART_REST 244 pm->bsdlabel[PART_REST].pi_offset = 0; 245 pm->bsdlabel[PART_REST].pi_size = pm->ptstart; 246 #endif 247 248 /* 249 * Save any partitions that are outside the area we are 250 * going to use. 251 * In particular this saves details of the other MBR 252 * partitions on a multiboot i386 system. 253 */ 254 for (i = maxpart; i--;) { 255 if (pm->bsdlabel[i].pi_size != 0) 256 /* Don't overwrite special partitions */ 257 continue; 258 p = &pm->oldlabel[i]; 259 if (p->pi_fstype == FS_UNUSED || p->pi_size == 0) 260 continue; 261 if (layoutkind == LY_USEEXIST) { 262 if (PI_ISBSDFS(p)) 263 p->pi_flags |= PIF_MOUNT; 264 } else { 265 if (p->pi_offset < pm->ptstart + pm->ptsize && 266 p->pi_offset + p->pi_size > pm->ptstart) 267 /* Not outside area we are allocating */ 268 continue; 269 if (p->pi_fstype == FS_SWAP) 270 no_swap = 1; 271 } 272 pm->bsdlabel[i] = pm->oldlabel[i]; 273 } 274 275 if (layoutkind == LY_USEEXIST) { 276 /* XXX Check we have a sensible layout */ 277 ; 278 } else 279 get_ptn_sizes(partstart, ptend - partstart, no_swap); 280 281 /* 282 * OK, we have a partition table. Give the user the chance to 283 * edit it and verify it's OK, or abort altogether. 284 */ 285 edit_check: 286 if (edit_and_check_label(pm->bsdlabel, maxpart, part_raw, part_bsd) == 0) { 287 msg_display(MSG_abort); 288 return 0; 289 } 290 if (md_check_partitions() == 0) 291 goto edit_check; 292 293 /* Disk name */ 294 msg_prompt(MSG_packname, pm->bsddiskname, pm->bsddiskname, sizeof pm->bsddiskname); 295 296 /* save label to disk for MI code to update. */ 297 (void) savenewlabel(pm->bsdlabel, maxpart); 298 299 /* Everything looks OK. */ 300 return 1; 301 #endif 302 303 return make_bsd_partitions(install); 304 } 305 306 /* 307 * any additional partition validation 308 */ 309 bool 310 md_check_partitions(struct install_partition_desc *install) 311 { 312 struct disk_partitions *parts; 313 struct disk_part_info info; 314 int fprep=0, ffat=0; 315 part_id part; 316 317 if (rdb_found) 318 return 1; 319 320 if (install->num < 1) 321 return false; 322 parts = install->infos[0].parts; /* disklabel parts */ 323 if (parts->parent) 324 parts = parts->parent; /* MBR parts */ 325 326 /* we need to find a boot partition, otherwise we can't create 327 * our msdos fs boot partition. We make the assumption that 328 * the user hasn't done something stupid, like move it away 329 * from the MBR partition. 330 */ 331 for (part = 0; part < parts->num_part; part++) { 332 if (!parts->pscheme->get_part_info(parts, part, &info)) 333 continue; 334 335 if (info.fs_type == FS_MSDOS) { 336 bootpart_fat12 = part; 337 ffat++; 338 } else if (info.fs_type == FS_BOOT) { 339 bootpart_prep = part; 340 fprep++; 341 } else if (info.fs_type == FS_OTHER) { 342 bootpart_binfo = part; 343 fprep++; 344 } 345 } 346 /* oh, the confusion */ 347 if (ffat >= 1 && fprep < 2) { 348 noprepfix = 1; 349 return true; 350 } 351 if (ffat < 1 && fprep >= 2) { 352 nobootfix = 1; 353 return true; 354 } 355 if (ffat >=1 && fprep >= 2) { 356 return true; 357 } 358 359 msg_display(MSG_nobootpartdisklabel); 360 process_menu(MENU_ok, NULL); 361 nobootfix = 1; 362 return false; 363 } 364 365 /* 366 * hook called before writing new disklabel. 367 */ 368 bool 369 md_pre_disklabel(struct install_partition_desc *install, 370 struct disk_partitions *parts) 371 { 372 373 if (rdb_found) 374 return true; 375 376 377 if (parts->parent == NULL) 378 return true; /* no outer partitions */ 379 380 parts = parts->parent; 381 382 msg_display_subst(MSG_dofdisk, 3, parts->disk, 383 msg_string(parts->pscheme->name), 384 msg_string(parts->pscheme->short_name)); 385 386 /* write edited "MBR" onto disk. */ 387 if (!parts->pscheme->write_to_disk(parts)) { 388 msg_display(MSG_wmbrfail); 389 process_menu(MENU_ok, NULL); 390 return false; 391 } 392 return true; 393 } 394 395 /* 396 * hook called after writing disklabel to new target disk. 397 */ 398 bool 399 md_post_disklabel(struct install_partition_desc *install, 400 struct disk_partitions *parts) 401 { 402 char bootdev[100]; 403 404 if (pm->bootstart == 0 || pm->bootsize == 0 || rdb_found) 405 return 0; 406 407 snprintf(bootdev, sizeof bootdev, "/dev/r%s%c", pm->diskdev, 408 (char)('a'+bootpart_fat12)); 409 run_program(RUN_DISPLAY, "/sbin/newfs_msdos %s", bootdev); 410 411 return 0; 412 } 413 414 /* 415 * hook called after upgrade() or install() has finished setting 416 * up the target disk but immediately before the user is given the 417 * ``disks are now set up'' message. 418 */ 419 int 420 md_post_newfs(struct install_partition_desc *install) 421 { 422 423 /* No bootblock. We use ofwboot from a partition visiable by OFW. */ 424 return 0; 425 } 426 427 int 428 md_post_extract(struct install_partition_desc *install) 429 { 430 char bootdev[100], bootbdev[100], version[64]; 431 struct disk_partitions *parts; 432 433 /* if we can't make it bootable, just punt */ 434 if ((nobootfix && noprepfix) || rdb_found) 435 return 0; 436 437 snprintf(version, sizeof version, "NetBSD/%s %s", MACH, REL); 438 run_program(RUN_DISPLAY, "/usr/mdec/mkbootinfo '%s' %d " 439 "/tmp/bootinfo.txt", version, bootinfo_mbr); 440 441 if (!nobootfix) { 442 run_program(RUN_DISPLAY, "/bin/mkdir -p /%s/boot/ppc", 443 target_prefix()); 444 run_program(RUN_DISPLAY, "/bin/mkdir -p /%s/boot/netbsd", 445 target_prefix()); 446 run_program(RUN_DISPLAY, "/bin/cp /usr/mdec/ofwboot " 447 "/%s/boot/netbsd", target_prefix()); 448 run_program(RUN_DISPLAY, "/bin/cp /tmp/bootinfo.txt " 449 "/%s/boot/ppc", target_prefix()); 450 run_program(RUN_DISPLAY, "/bin/cp /usr/mdec/ofwboot " 451 "/%s/boot/ofwboot", target_prefix()); 452 } 453 454 if (!noprepfix && install != NULL && install->num > 0) { 455 parts = install->infos[0].parts; /* disklabel */ 456 if (parts->parent != NULL) 457 parts = parts->parent; /* MBR */ 458 459 parts->pscheme->get_part_device(parts, bootpart_prep, 460 bootdev, sizeof bootdev, NULL, raw_dev_name, true, true); 461 parts->pscheme->get_part_device(parts, bootpart_prep, 462 bootbdev, sizeof bootbdev, NULL, plain_name, true, true); 463 run_program(RUN_DISPLAY, "/bin/dd if=/dev/zero of=%s bs=512", 464 bootdev); 465 run_program(RUN_DISPLAY, "/bin/dd if=/usr/mdec/ofwboot " 466 "of=%s bs=512", bootbdev); 467 468 parts->pscheme->get_part_device(parts, bootpart_binfo, 469 bootdev, sizeof bootdev, NULL, raw_dev_name, true, true); 470 parts->pscheme->get_part_device(parts, bootpart_binfo, 471 bootbdev, sizeof bootbdev, NULL, plain_name, true, true); 472 run_program(RUN_DISPLAY, "/bin/dd if=/dev/zero of=%s bs=512", 473 bootdev); 474 run_program(RUN_DISPLAY, "/bin/dd if=/tmp/bootinfo.txt " 475 "of=%s bs=512", bootbdev); 476 } 477 478 return 0; 479 } 480 481 void 482 md_cleanup_install(struct install_partition_desc *install) 483 { 484 485 #ifndef DEBUG 486 enable_rc_conf(); 487 #endif 488 } 489 490 int 491 md_pre_update(struct install_partition_desc *install) 492 { 493 #if 0 494 struct mbr_partition *part; 495 mbr_info_t *ext; 496 int i; 497 #endif 498 499 if (check_rdb()) 500 return 1; 501 502 #if 0 503 read_mbr(pm->diskdev, &mbr); 504 /* do a sanity check of the partition table */ 505 for (ext = &mbr; ext; ext = ext->extended) { 506 part = ext->mbr.mbr_parts; 507 for (i = 0; i < MBR_PART_COUNT; part++, i++) { 508 if (part->mbrp_type == MBR_PTYPE_PREP && 509 part->mbrp_size > 50) 510 bootinfo_mbr = i+1; 511 if (part->mbrp_type == MBR_PTYPE_RESERVED_x21 && 512 part->mbrp_size < (MIN_FAT12_BOOT/512)) { 513 msg_display(MSG_boottoosmall); 514 msg_fmt_display_add(MSG_nobootpartdisklabel, 515 "%d", 0); 516 if (!ask_yesno(NULL)) 517 return 0; 518 nobootfix = 1; 519 } 520 } 521 } 522 #endif 523 524 if (!md_check_partitions(install)) 525 return 0; 526 527 return 1; 528 } 529 530 /* Upgrade support */ 531 int 532 md_update(struct install_partition_desc *install) 533 { 534 535 nonewfsmsdos = 1; 536 md_post_newfs(install); 537 return 1; 538 } 539 540 541 int 542 md_check_mbr(struct disk_partitions *parts, mbr_info_t *mbri, bool quiet) 543 { 544 mbr_info_t *ext; 545 struct mbr_partition *part; 546 int i; 547 548 for (ext = mbri; ext; ext = ext->extended) { 549 part = ext->mbr.mbr_parts; 550 for (i = 0; i < MBR_PART_COUNT; part++, i++) { 551 if (part->mbrp_type == MBR_PTYPE_FAT12) { 552 pm->bootstart = part->mbrp_start; 553 pm->bootsize = part->mbrp_size; 554 } else if (part->mbrp_type == MBR_PTYPE_PREP && 555 part->mbrp_size < 50) { 556 /* this is the bootinfo partition */ 557 binfostart = part->mbrp_start; 558 binfosize = part->mbrp_size; 559 bootinfo_mbr = i+1; 560 } else if (part->mbrp_type == MBR_PTYPE_PREP && 561 part->mbrp_size > 50) { 562 bprepstart = part->mbrp_start; 563 bprepsize = part->mbrp_size; 564 } 565 break; 566 } 567 } 568 569 /* we need to either have a pair of prep partitions, or a single 570 * fat. if neither, things are broken. */ 571 if (!(pm->bootsize >= (MIN_FAT12_BOOT/512) || 572 (binfosize >= (MIN_BINFO_BOOT/512) && 573 bprepsize >= (MIN_PREP_BOOT/512)))) { 574 if (quiet) 575 return 0; 576 msg_display(MSG_bootnotright); 577 return ask_reedit(parts); 578 } 579 580 /* check the prep partitions */ 581 if ((binfosize > 0 || bprepsize > 0) && 582 (binfosize < (MIN_BINFO_BOOT/512) || 583 bprepsize < (MIN_PREP_BOOT/512))) { 584 if (quiet) 585 return 0; 586 msg_display(MSG_preptoosmall); 587 return ask_reedit(parts); 588 } 589 590 /* check the fat12 parititons */ 591 if (pm->bootsize > 0 && pm->bootsize < (MIN_FAT12_BOOT/512)) { 592 if (quiet) 593 return 0; 594 msg_display(MSG_boottoosmall); 595 return ask_reedit(parts); 596 } 597 598 /* if both sets contain zero, thats bad */ 599 if ((pm->bootstart == 0 || pm->bootsize == 0) && 600 (binfosize == 0 || binfostart == 0 || 601 bprepsize == 0 || bprepstart == 0)) { 602 if (quiet) 603 return 0; 604 msg_display(MSG_nobootpart); 605 return ask_reedit(parts); 606 } 607 return 2; 608 } 609 610 /* 611 * NOTE, we use a reserved partition type, because some RS/6000 machines hang 612 * hard if they find a FAT12, and if we use type prep, that indicates that 613 * it should be read raw. 614 * One partition for FAT12 booting 615 * One partition for NetBSD 616 * One partition to hold the bootinfo.txt file 617 * One partition to hold ofwboot 618 */ 619 620 bool 621 md_parts_use_wholedisk(struct disk_partitions *parts) 622 { 623 struct disk_part_info boot_parts[] = 624 { 625 { .fs_type = FS_MSDOS, .size = FAT12_BOOT_SIZE/512 }, 626 { .fs_type = FS_OTHER, .size = BINFO_BOOT_SIZE/512 }, 627 { .fs_type = FS_BOOT, .size = PREP_BOOT_SIZE/512 } 628 }; 629 630 return parts_use_wholedisk(parts, __arraycount(boot_parts), 631 boot_parts); 632 } 633 634 const char *md_disklabel_cmd(void) 635 { 636 637 /* we cannot rewrite an RDB disklabel */ 638 if (rdb_found) 639 return "sync No disklabel"; 640 641 return "disklabel -w -r"; 642 } 643 644 static int 645 check_rdb(void) 646 { 647 char buf[512], diskpath[MAXPATHLEN]; 648 struct rdblock *rdb; 649 off_t blk; 650 int fd; 651 652 /* Find out if this disk has a valid RDB, before continuing. */ 653 rdb = (struct rdblock *)buf; 654 fd = opendisk(pm->diskdev, O_RDONLY, diskpath, sizeof(diskpath), 0); 655 if (fd < 0) 656 return 0; 657 for (blk = 0; blk < RDB_MAXBLOCKS; blk++) { 658 if (pread(fd, rdb, 512, blk * 512) != 512) 659 return 0; 660 if (rdb->id == RDBLOCK_ID && rdbchksum(rdb) == 0) { 661 rdb_found = 1; /* do not repartition! */ 662 return 1; 663 } 664 } 665 return 0; 666 } 667 668 static uint32_t 669 rdbchksum(void *bdata) 670 { 671 uint32_t *blp, cnt, val; 672 673 blp = bdata; 674 cnt = blp[1]; 675 val = 0; 676 while (cnt--) 677 val += *blp++; 678 return val; 679 } 680 681 int 682 md_pre_mount(struct install_partition_desc *install, size_t ndx) 683 { 684 685 return 0; 686 } 687 688 bool 689 md_mbr_update_check(struct disk_partitions *parts, mbr_info_t *mbri) 690 { 691 return false; /* no change, no need to write back */ 692 } 693 694 #ifdef HAVE_GPT 695 bool 696 md_gpt_post_write(struct disk_partitions *parts, part_id root_id, 697 bool root_is_new, part_id efi_id, bool efi_is_new) 698 { 699 /* no GPT boot support, nothing needs to be done here */ 700 return true; 701 } 702 #endif 703 704