1 /* 2 * Copyright (c) 2007 The DragonFly Project. All rights reserved. 3 * 4 * This code is derived from software contributed to The DragonFly Project 5 * by Matthew Dillon <dillon@backplane.com> 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in 15 * the documentation and/or other materials provided with the 16 * distribution. 17 * 3. Neither the name of The DragonFly Project nor the names of its 18 * contributors may be used to endorse or promote products derived 19 * from this software without specific, prior written permission. 20 * 21 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 22 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 23 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 24 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 25 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 26 * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, 27 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 28 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 29 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 30 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 31 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 32 * SUCH DAMAGE. 33 */ 34 35 #include <sys/sysctl.h> 36 #include <sys/ioctl_compat.h> 37 38 #include "hammer_util.h" 39 40 static int64_t getsize(const char *str, int64_t minval, int64_t maxval, int pw); 41 static int trim_volume(struct volume_info *vol); 42 static void format_volume(struct volume_info *vol, int nvols,const char *label); 43 static hammer_off_t format_root_directory(const char *label); 44 static uint64_t nowtime(void); 45 static void usage(int exit_code); 46 47 static int ForceOpt; 48 static int64_t BootAreaSize; 49 static int64_t MemAreaSize; 50 static int64_t UndoBufferSize; 51 static int HammerVersion = -1; 52 53 #define GIG (1024LL*1024*1024) 54 55 int 56 main(int ac, char **av) 57 { 58 uint32_t status; 59 off_t total; 60 off_t avg_vol_size; 61 int ch; 62 int i; 63 int nvols; 64 int eflag = 0; 65 const char *label = NULL; 66 struct volume_info *vol; 67 char *fsidstr; 68 69 /* 70 * Sanity check basic filesystem structures. No cookies for us 71 * if it gets broken! 72 */ 73 assert(sizeof(struct hammer_volume_ondisk) <= HAMMER_BUFSIZE); 74 assert(sizeof(struct hammer_blockmap_layer1) == 32); 75 assert(sizeof(struct hammer_blockmap_layer2) == 16); 76 77 /* 78 * Parse arguments 79 */ 80 while ((ch = getopt(ac, av, "dfEL:b:m:u:hC:V:")) != -1) { 81 switch(ch) { 82 case 'd': 83 ++DebugOpt; 84 break; 85 case 'f': 86 ForceOpt = 1; 87 break; 88 case 'E': 89 eflag = 1; 90 break; 91 case 'L': 92 label = optarg; 93 break; 94 case 'b': 95 BootAreaSize = getsize(optarg, 96 HAMMER_BUFSIZE, 97 HAMMER_BOOT_MAXBYTES, 2); 98 break; 99 case 'm': 100 MemAreaSize = getsize(optarg, 101 HAMMER_BUFSIZE, 102 HAMMER_MEM_MAXBYTES, 2); 103 break; 104 case 'u': 105 UndoBufferSize = getsize(optarg, 106 HAMMER_BIGBLOCK_SIZE, 107 HAMMER_BIGBLOCK_SIZE * 108 HAMMER_UNDO_LAYER2, 2); 109 if (UndoBufferSize < 500*1024*1024 && ForceOpt == 0) 110 errx(1, "The minimum UNDO/REDO FIFO size is " 111 "500MB\n"); 112 if (UndoBufferSize < 500*1024*1024) { 113 fprintf(stderr, 114 "WARNING: you have specified an " 115 "UNDO/REDO FIFO size less than 500MB,\n" 116 "which may lead to VFS panics.\n"); 117 } 118 break; 119 case 'h': 120 usage(0); 121 break; 122 case 'C': 123 if (hammer_parse_cache_size(optarg) == -1) 124 usage(1); 125 break; 126 case 'V': 127 HammerVersion = strtol(optarg, NULL, 0); 128 if (HammerVersion < HAMMER_VOL_VERSION_MIN || 129 HammerVersion >= HAMMER_VOL_VERSION_WIP) { 130 errx(1, 131 "I don't understand how to format " 132 "HAMMER version %d\n", 133 HammerVersion); 134 } 135 break; 136 default: 137 usage(1); 138 break; 139 } 140 } 141 ac -= optind; 142 av += optind; 143 nvols = ac; 144 145 if (label == NULL) { 146 fprintf(stderr, 147 "newfs_hammer: A filesystem label must be specified\n"); 148 usage(1); 149 } 150 151 if (HammerVersion < 0) { 152 size_t olen = sizeof(HammerVersion); 153 HammerVersion = HAMMER_VOL_VERSION_DEFAULT; 154 if (sysctlbyname("vfs.hammer.supported_version", 155 &HammerVersion, &olen, NULL, 0) == 0) { 156 if (HammerVersion >= HAMMER_VOL_VERSION_WIP) { 157 HammerVersion = HAMMER_VOL_VERSION_WIP - 1; 158 fprintf(stderr, 159 "newfs_hammer: WARNING: HAMMER VFS " 160 "supports higher version than I " 161 "understand,\n" 162 "using version %d\n", 163 HammerVersion); 164 } 165 } else { 166 fprintf(stderr, 167 "newfs_hammer: WARNING: HAMMER VFS not " 168 "loaded, cannot get version info.\n" 169 "Using version %d\n", 170 HAMMER_VOL_VERSION_DEFAULT); 171 } 172 } 173 174 if (nvols == 0) { 175 fprintf(stderr, 176 "newfs_hammer: You must specify at least one " 177 "special file (volume)\n"); 178 exit(1); 179 } 180 181 if (nvols > HAMMER_MAX_VOLUMES) { 182 fprintf(stderr, 183 "newfs_hammer: The maximum number of volumes is %d\n", 184 HAMMER_MAX_VOLUMES); 185 exit(1); 186 } 187 188 /* 189 * Generate a filesystem id and lookup the filesystem type 190 */ 191 uuidgen(&Hammer_FSId, 1); 192 uuid_name_lookup(&Hammer_FSType, HAMMER_FSTYPE_STRING, &status); 193 if (status != uuid_s_ok) { 194 errx(1, "uuids file does not have the DragonFly " 195 "HAMMER filesystem type"); 196 } 197 198 total = 0; 199 for (i = 0; i < nvols; ++i) { 200 vol = init_volume(av[i], O_RDWR, i); 201 printf("Volume %d %s %-15s size %s\n", 202 vol->vol_no, vol->type, vol->name, 203 sizetostr(vol->size)); 204 205 if (eflag) { 206 int res = trim_volume(vol); 207 if (res == -1 || (res == 1 && ForceOpt == 0)) 208 exit(1); 209 } 210 total += vol->size; 211 } 212 213 /* 214 * Calculate defaults for the boot and memory area sizes. 215 */ 216 avg_vol_size = total / nvols; 217 BootAreaSize = init_boot_area_size(BootAreaSize, avg_vol_size); 218 MemAreaSize = init_mem_area_size(MemAreaSize, avg_vol_size); 219 220 /* 221 * Format the volumes. Format the root volume first so we can 222 * bootstrap the freemap. 223 */ 224 format_volume(get_root_volume(), nvols, label); 225 for (i = 0; i < nvols; ++i) { 226 if (i != HAMMER_ROOT_VOLNO) 227 format_volume(get_volume(i), nvols, label); 228 } 229 230 /* 231 * Print information stored in the root volume header. 232 */ 233 vol = get_root_volume(); 234 uuid_to_string(&Hammer_FSId, &fsidstr, &status); 235 236 printf("---------------------------------------------\n"); 237 printf("%d volume%s total size %s version %d\n", 238 nvols, (nvols == 1 ? "" : "s"), 239 sizetostr(total), HammerVersion); 240 printf("root-volume: %s\n", vol->name); 241 printf("boot-area-size: %s\n", sizetostr(BootAreaSize)); 242 printf("memory-log-size: %s\n", sizetostr(MemAreaSize)); 243 printf("undo-buffer-size: %s\n", sizetostr(UndoBufferSize)); 244 printf("total-pre-allocated: %s\n", 245 sizetostr(vol->vol_free_off & HAMMER_OFF_SHORT_MASK)); 246 printf("fsid: %s\n", fsidstr); 247 printf("\n"); 248 printf("NOTE: Please remember that you may have to manually set up a\n" 249 "cron(8) job to prune and reblock the filesystem regularly.\n" 250 "By default, the system automatically runs 'hammer cleanup'\n" 251 "on a nightly basis. The periodic.conf(5) variable\n" 252 "'daily_clean_hammer_enable' can be unset to disable this.\n" 253 "Also see 'man hammer' and 'man HAMMER' for more information.\n"); 254 if (total < 10*GIG) { 255 printf("\nWARNING: The minimum UNDO/REDO FIFO is 500MB, you " 256 "really should not\n" 257 "try to format a HAMMER filesystem this small.\n"); 258 } 259 if (total < 50*GIG) { 260 printf("\nWARNING: HAMMER filesystems less than 50GB are " 261 "not recommended!\n" 262 "You may have to run 'hammer prune-everything' and " 263 "'hammer reblock'\n" 264 "quite often, even if using a nohistory mount.\n"); 265 } 266 flush_all_volumes(); 267 return(0); 268 } 269 270 static 271 void 272 usage(int exit_code) 273 { 274 fprintf(stderr, 275 "usage: newfs_hammer -L label [-Efh] [-b bootsize] [-m savesize] [-u undosize]\n" 276 " [-C cachesize[:readahead]] [-V version] special ...\n" 277 ); 278 exit(exit_code); 279 } 280 281 /* 282 * Convert a string to a 64 bit signed integer with various requirements. 283 */ 284 static int64_t 285 getsize(const char *str, int64_t minval, int64_t maxval, int powerof2) 286 { 287 int64_t val; 288 char *ptr; 289 290 val = strtoll(str, &ptr, 0); 291 switch(*ptr) { 292 case 't': 293 case 'T': 294 val *= 1024; 295 /* fall through */ 296 case 'g': 297 case 'G': 298 val *= 1024; 299 /* fall through */ 300 case 'm': 301 case 'M': 302 val *= 1024; 303 /* fall through */ 304 case 'k': 305 case 'K': 306 val *= 1024; 307 break; 308 default: 309 errx(1, "Unknown suffix in number '%s'\n", str); 310 /* not reached */ 311 } 312 if (ptr[1]) { 313 errx(1, "Unknown suffix in number '%s'\n", str); 314 /* not reached */ 315 } 316 if (val < minval) { 317 errx(1, "Value too small: %s, min is %s\n", 318 str, sizetostr(minval)); 319 /* not reached */ 320 } 321 if (val > maxval) { 322 errx(1, "Value too large: %s, max is %s\n", 323 str, sizetostr(maxval)); 324 /* not reached */ 325 } 326 if ((powerof2 & 1) && (val ^ (val - 1)) != ((val << 1) - 1)) { 327 errx(1, "Value not power of 2: %s\n", str); 328 /* not reached */ 329 } 330 if ((powerof2 & 2) && (val & HAMMER_BUFMASK)) { 331 errx(1, "Value not an integral multiple of %dK: %s", 332 HAMMER_BUFSIZE / 1024, str); 333 /* not reached */ 334 } 335 return(val); 336 } 337 338 /* 339 * Generate a transaction id. Transaction ids are no longer time-based. 340 * Put the nail in the coffin by not making the first one time-based. 341 * 342 * We could start at 1 here but start at 2^32 to reserve a small domain for 343 * possible future use. 344 */ 345 static hammer_tid_t 346 createtid(void) 347 { 348 static hammer_tid_t lasttid; 349 350 if (lasttid == 0) 351 lasttid = 0x0000000100000000ULL; 352 return(lasttid++); 353 } 354 355 static uint64_t 356 nowtime(void) 357 { 358 struct timeval tv; 359 uint64_t xtime; 360 361 gettimeofday(&tv, NULL); 362 xtime = tv.tv_sec * 1000000LL + tv.tv_usec; 363 return(xtime); 364 } 365 366 /* 367 * TRIM the volume, but only if the backing store is a DEVICE 368 */ 369 static 370 int 371 trim_volume(struct volume_info *vol) 372 { 373 size_t olen; 374 char *dev_name, *p; 375 char sysctl_name[64]; 376 int trim_enabled; 377 off_t ioarg[2]; 378 379 if (strcmp(vol->type, "DEVICE")) { 380 fprintf(stderr, "Cannot TRIM regular file %s\n", vol->name); 381 return(1); 382 } 383 if (strncmp(vol->name, "/dev/da", 7)) { 384 fprintf(stderr, "%s does not support the TRIM command\n", 385 vol->name); 386 return(1); 387 } 388 389 /* Extract a number from /dev/da?s? */ 390 dev_name = strdup(vol->name); 391 p = strtok(dev_name + strlen("/dev/da"), "s"); 392 sprintf(sysctl_name, "kern.cam.da.%s.trim_enabled", p); 393 free(dev_name); 394 395 trim_enabled = 0; 396 olen = sizeof(trim_enabled); 397 398 if (sysctlbyname(sysctl_name, &trim_enabled, &olen, NULL, 0) == -1) { 399 fprintf(stderr, "%s (%s) does not support the TRIM command\n", 400 vol->name, sysctl_name); 401 return(1); 402 } 403 if (!trim_enabled) { 404 fprintf(stderr, "Erase device option selected, but sysctl (%s) " 405 "is not enabled\n", sysctl_name); 406 return(1); 407 } 408 409 ioarg[0] = vol->device_offset; 410 ioarg[1] = vol->size; 411 412 printf("Trimming %s %s, sectors %llu-%llu\n", 413 vol->type, vol->name, 414 (unsigned long long)ioarg[0] / 512, 415 (unsigned long long)ioarg[1] / 512); 416 417 if (ioctl(vol->fd, IOCTLTRIM, ioarg) == -1) { 418 fprintf(stderr, "Device trim failed\n"); 419 return(-1); 420 } 421 422 return(0); 423 } 424 425 /* 426 * Format a HAMMER volume. 427 */ 428 static 429 void 430 format_volume(struct volume_info *vol, int nvols, const char *label) 431 { 432 struct volume_info *root_vol; 433 hammer_volume_ondisk_t ondisk; 434 int64_t freeblks; 435 int64_t freebytes; 436 int64_t vol_buf_size; 437 hammer_off_t vol_alloc; 438 int i; 439 440 /* 441 * Initialize basic information in the on-disk volume structure. 442 */ 443 ondisk = vol->ondisk; 444 445 ondisk->vol_fsid = Hammer_FSId; 446 ondisk->vol_fstype = Hammer_FSType; 447 snprintf(ondisk->vol_label, sizeof(ondisk->vol_label), "%s", label); 448 ondisk->vol_no = vol->vol_no; 449 ondisk->vol_count = nvols; 450 ondisk->vol_version = HammerVersion; 451 452 /* 453 * Reserve space for (future) header junk, setup our poor-man's 454 * big-block allocator. 455 */ 456 vol_alloc = HAMMER_VOL_ALLOC; 457 458 ondisk->vol_bot_beg = vol_alloc; 459 vol_alloc += BootAreaSize; 460 ondisk->vol_mem_beg = vol_alloc; 461 vol_alloc += MemAreaSize; 462 463 /* 464 * The remaining area is the zone 2 buffer allocation area. 465 */ 466 ondisk->vol_buf_beg = vol_alloc; 467 ondisk->vol_buf_end = vol->size & ~(int64_t)HAMMER_BUFMASK; 468 vol_buf_size = HAMMER_VOL_BUF_SIZE(ondisk); 469 470 if (vol_buf_size < 0) { 471 errx(1, "volume %d %s is too small to hold the volume header", 472 vol->vol_no, vol->name); 473 } 474 475 if ((vol_buf_size & ~HAMMER_OFF_SHORT_MASK) != 0) { 476 errx(1, "volume %d %s is too large", vol->vol_no, vol->name); 477 } 478 479 ondisk->vol_rootvol = HAMMER_ROOT_VOLNO; 480 ondisk->vol_signature = HAMMER_FSBUF_VOLUME; 481 482 vol->vol_free_off = HAMMER_ENCODE_RAW_BUFFER(vol->vol_no, 0); 483 vol->vol_free_end = HAMMER_ENCODE_RAW_BUFFER(vol->vol_no, 484 vol_buf_size & ~HAMMER_BIGBLOCK_MASK64); 485 486 /* 487 * Format the root volume. 488 */ 489 if (vol->vol_no == HAMMER_ROOT_VOLNO) { 490 /* 491 * Check freemap counts before formatting 492 */ 493 freeblks = count_freemap(vol); 494 freebytes = freeblks * HAMMER_BIGBLOCK_SIZE64; 495 if (freebytes < 10*GIG && ForceOpt == 0) { 496 errx(1, "Cannot create a HAMMER filesystem less than 10GB " 497 "unless you use -f\n(for the size of Volume %d). " 498 "HAMMER filesystems less than 50GB are not " 499 "recommended.\n", HAMMER_ROOT_VOLNO); 500 } 501 502 /* 503 * Starting TID 504 */ 505 ondisk->vol0_next_tid = createtid(); 506 507 /* 508 * Format freemap. vol0_stat_freebigblocks is 509 * the number of big-blocks available for anything 510 * other than freemap zone at this point. 511 */ 512 format_freemap(vol); 513 assert(ondisk->vol0_stat_freebigblocks == 0); 514 ondisk->vol0_stat_freebigblocks = initialize_freemap(vol); 515 516 /* 517 * Format zones that are mapped to zone-2. 518 */ 519 for (i = 0; i < HAMMER_MAX_ZONES; ++i) { 520 if (hammer_is_zone2_mapped_index(i)) 521 format_blockmap(vol, i, 0); 522 } 523 524 /* 525 * Format undo zone. Formatting decrements 526 * vol0_stat_freebigblocks whenever a new big-block 527 * is allocated for undo zone. 528 */ 529 format_undomap(vol, &UndoBufferSize); 530 assert(ondisk->vol0_stat_bigblocks == 0); 531 ondisk->vol0_stat_bigblocks = ondisk->vol0_stat_freebigblocks; 532 533 /* 534 * Format the root directory. Formatting decrements 535 * vol0_stat_freebigblocks whenever a new big-block 536 * is allocated for required zones. 537 */ 538 ondisk->vol0_btree_root = format_root_directory(label); 539 ++ondisk->vol0_stat_inodes; /* root inode */ 540 } else { 541 freeblks = initialize_freemap(vol); 542 root_vol = get_root_volume(); 543 root_vol->ondisk->vol0_stat_freebigblocks += freeblks; 544 root_vol->ondisk->vol0_stat_bigblocks += freeblks; 545 } 546 } 547 548 /* 549 * Format the root directory. 550 */ 551 static 552 hammer_off_t 553 format_root_directory(const char *label) 554 { 555 hammer_off_t btree_off; 556 hammer_off_t idata_off; 557 hammer_off_t pfsd_off; 558 hammer_tid_t create_tid; 559 hammer_node_ondisk_t bnode; 560 hammer_inode_data_t idata; 561 hammer_pseudofs_data_t pfsd; 562 struct buffer_info *data_buffer0 = NULL; 563 struct buffer_info *data_buffer1 = NULL; 564 struct buffer_info *data_buffer2 = NULL; 565 hammer_btree_elm_t elm; 566 uint64_t xtime; 567 568 /* 569 * Allocate zero-filled root btree node, inode and pfs 570 */ 571 bnode = alloc_btree_node(&btree_off, &data_buffer0); 572 idata = alloc_meta_element(&idata_off, sizeof(*idata), &data_buffer1); 573 pfsd = alloc_meta_element(&pfsd_off, sizeof(*pfsd), &data_buffer2); 574 create_tid = createtid(); 575 xtime = nowtime(); 576 577 /* 578 * Populate the inode data and inode record for the root directory. 579 */ 580 idata->version = HAMMER_INODE_DATA_VERSION; 581 idata->mode = 0755; 582 idata->ctime = xtime; 583 idata->mtime = xtime; 584 idata->atime = xtime; 585 idata->obj_type = HAMMER_OBJTYPE_DIRECTORY; 586 idata->size = 0; 587 idata->nlinks = 1; 588 if (HammerVersion >= HAMMER_VOL_VERSION_TWO) 589 idata->cap_flags |= HAMMER_INODE_CAP_DIR_LOCAL_INO; 590 if (HammerVersion >= HAMMER_VOL_VERSION_SIX) 591 idata->cap_flags |= HAMMER_INODE_CAP_DIRHASH_ALG1; 592 593 /* 594 * Populate the PFS data for the root PFS. 595 */ 596 pfsd->sync_low_tid = 1; 597 pfsd->sync_beg_tid = 0; 598 pfsd->sync_end_tid = 0; /* overriden by vol0_next_tid on pfs0 */ 599 pfsd->shared_uuid = Hammer_FSId; 600 pfsd->unique_uuid = Hammer_FSId; 601 pfsd->mirror_flags = 0; 602 snprintf(pfsd->label, sizeof(pfsd->label), "%s", label); 603 604 /* 605 * Create the root of the B-Tree. The root is a leaf node so we 606 * do not have to worry about boundary elements. 607 */ 608 bnode->parent = 0; /* no parent */ 609 bnode->count = 2; 610 bnode->type = HAMMER_BTREE_TYPE_LEAF; 611 bnode->mirror_tid = 0; 612 613 /* 614 * Create the first node element for the inode. 615 */ 616 elm = &bnode->elms[0]; 617 elm->leaf.base.btype = HAMMER_BTREE_TYPE_RECORD; 618 elm->leaf.base.localization = HAMMER_DEF_LOCALIZATION | 619 HAMMER_LOCALIZE_INODE; 620 elm->leaf.base.obj_id = HAMMER_OBJID_ROOT; 621 elm->leaf.base.key = 0; 622 elm->leaf.base.create_tid = create_tid; 623 elm->leaf.base.delete_tid = 0; 624 elm->leaf.base.rec_type = HAMMER_RECTYPE_INODE; 625 elm->leaf.base.obj_type = HAMMER_OBJTYPE_DIRECTORY; 626 elm->leaf.create_ts = (uint32_t)time(NULL); 627 628 elm->leaf.data_offset = idata_off; 629 elm->leaf.data_len = sizeof(*idata); 630 hammer_crc_set_leaf(idata, &elm->leaf); 631 632 /* 633 * Create the second node element for the PFS data. 634 * This is supposed to be a record part of the root ip (inode), 635 * so it should have the same obj_type value as above. 636 */ 637 elm = &bnode->elms[1]; 638 elm->leaf.base.btype = HAMMER_BTREE_TYPE_RECORD; 639 elm->leaf.base.localization = HAMMER_DEF_LOCALIZATION | 640 HAMMER_LOCALIZE_MISC; 641 elm->leaf.base.obj_id = HAMMER_OBJID_ROOT; 642 elm->leaf.base.key = 0; 643 elm->leaf.base.create_tid = create_tid; 644 elm->leaf.base.delete_tid = 0; 645 elm->leaf.base.rec_type = HAMMER_RECTYPE_PFS; 646 elm->leaf.base.obj_type = HAMMER_OBJTYPE_DIRECTORY; 647 elm->leaf.create_ts = (uint32_t)time(NULL); 648 649 elm->leaf.data_offset = pfsd_off; 650 elm->leaf.data_len = sizeof(*pfsd); 651 hammer_crc_set_leaf(pfsd, &elm->leaf); 652 653 hammer_crc_set_btree(bnode); 654 655 rel_buffer(data_buffer0); 656 rel_buffer(data_buffer1); 657 rel_buffer(data_buffer2); 658 659 return(btree_off); 660 } 661