1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * Copyright (c) 2009-2010 The FreeBSD Foundation 5 * 6 * This software was developed by Pawel Jakub Dawidek under sponsorship from 7 * the FreeBSD Foundation. 8 * 9 * Redistribution and use in source and binary forms, with or without 10 * modification, are permitted provided that the following conditions 11 * are met: 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 2. Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in the 16 * documentation and/or other materials provided with the distribution. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND 19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE 22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 24 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 28 * SUCH DAMAGE. 29 */ 30 31 #include <sys/param.h> /* powerof2() */ 32 #include <sys/queue.h> 33 34 #include <bitstring.h> 35 #include <errno.h> 36 #include <stdint.h> 37 #include <stdio.h> 38 #include <stdlib.h> 39 #include <string.h> 40 41 #include <pjdlog.h> 42 43 #include "activemap.h" 44 45 #ifndef PJDLOG_ASSERT 46 #include <assert.h> 47 #define PJDLOG_ASSERT(...) assert(__VA_ARGS__) 48 #endif 49 50 #define ACTIVEMAP_MAGIC 0xac71e4 51 struct activemap { 52 int am_magic; /* Magic value. */ 53 off_t am_mediasize; /* Media size in bytes. */ 54 uint32_t am_extentsize; /* Extent size in bytes, 55 must be power of 2. */ 56 uint8_t am_extentshift;/* 2 ^ extentbits == extentsize */ 57 int am_nextents; /* Number of extents. */ 58 size_t am_mapsize; /* Bitmap size in bytes. */ 59 uint16_t *am_memtab; /* An array that holds number of pending 60 writes per extent. */ 61 bitstr_t *am_diskmap; /* On-disk bitmap of dirty extents. */ 62 bitstr_t *am_memmap; /* In-memory bitmap of dirty extents. */ 63 size_t am_diskmapsize; /* Map size rounded up to sector size. */ 64 uint64_t am_ndirty; /* Number of dirty regions. */ 65 bitstr_t *am_syncmap; /* Bitmap of extents to sync. */ 66 off_t am_syncoff; /* Next synchronization offset. */ 67 TAILQ_HEAD(skeepdirty, keepdirty) am_keepdirty; /* List of extents that 68 we keep dirty to reduce bitmap 69 updates. */ 70 int am_nkeepdirty; /* Number of am_keepdirty elements. */ 71 int am_nkeepdirty_limit; /* Maximum number of am_keepdirty 72 elements. */ 73 }; 74 75 struct keepdirty { 76 int kd_extent; 77 TAILQ_ENTRY(keepdirty) kd_next; 78 }; 79 80 /* 81 * Helper function taken from sys/systm.h to calculate extentshift. 82 */ 83 static uint32_t 84 bitcount32(uint32_t x) 85 { 86 87 x = (x & 0x55555555) + ((x & 0xaaaaaaaa) >> 1); 88 x = (x & 0x33333333) + ((x & 0xcccccccc) >> 2); 89 x = (x + (x >> 4)) & 0x0f0f0f0f; 90 x = (x + (x >> 8)); 91 x = (x + (x >> 16)) & 0x000000ff; 92 return (x); 93 } 94 95 static __inline int 96 off2ext(const struct activemap *amp, off_t offset) 97 { 98 int extent; 99 100 PJDLOG_ASSERT(offset >= 0 && offset < amp->am_mediasize); 101 extent = (offset >> amp->am_extentshift); 102 PJDLOG_ASSERT(extent >= 0 && extent < amp->am_nextents); 103 return (extent); 104 } 105 106 static __inline off_t 107 ext2off(const struct activemap *amp, int extent) 108 { 109 off_t offset; 110 111 PJDLOG_ASSERT(extent >= 0 && extent < amp->am_nextents); 112 offset = ((off_t)extent << amp->am_extentshift); 113 PJDLOG_ASSERT(offset >= 0 && offset < amp->am_mediasize); 114 return (offset); 115 } 116 117 /* 118 * Function calculates number of requests needed to synchronize the given 119 * extent. 120 */ 121 static __inline int 122 ext2reqs(const struct activemap *amp, int ext) 123 { 124 off_t left; 125 126 if (ext < amp->am_nextents - 1) 127 return (((amp->am_extentsize - 1) / MAXPHYS) + 1); 128 129 PJDLOG_ASSERT(ext == amp->am_nextents - 1); 130 left = amp->am_mediasize % amp->am_extentsize; 131 if (left == 0) 132 left = amp->am_extentsize; 133 return (((left - 1) / MAXPHYS) + 1); 134 } 135 136 /* 137 * Initialize activemap structure and allocate memory for internal needs. 138 * Function returns 0 on success and -1 if any of the allocations failed. 139 */ 140 int 141 activemap_init(struct activemap **ampp, uint64_t mediasize, uint32_t extentsize, 142 uint32_t sectorsize, uint32_t keepdirty) 143 { 144 struct activemap *amp; 145 146 PJDLOG_ASSERT(ampp != NULL); 147 PJDLOG_ASSERT(mediasize > 0); 148 PJDLOG_ASSERT(extentsize > 0); 149 PJDLOG_ASSERT(powerof2(extentsize)); 150 PJDLOG_ASSERT(sectorsize > 0); 151 PJDLOG_ASSERT(powerof2(sectorsize)); 152 PJDLOG_ASSERT(keepdirty > 0); 153 154 amp = malloc(sizeof(*amp)); 155 if (amp == NULL) 156 return (-1); 157 158 amp->am_mediasize = mediasize; 159 amp->am_nkeepdirty_limit = keepdirty; 160 amp->am_extentsize = extentsize; 161 amp->am_extentshift = bitcount32(extentsize - 1); 162 amp->am_nextents = ((mediasize - 1) / extentsize) + 1; 163 amp->am_mapsize = bitstr_size(amp->am_nextents); 164 amp->am_diskmapsize = roundup2(amp->am_mapsize, sectorsize); 165 amp->am_ndirty = 0; 166 amp->am_syncoff = -2; 167 TAILQ_INIT(&->am_keepdirty); 168 amp->am_nkeepdirty = 0; 169 170 amp->am_memtab = calloc(amp->am_nextents, sizeof(amp->am_memtab[0])); 171 amp->am_diskmap = calloc(1, amp->am_diskmapsize); 172 amp->am_memmap = bit_alloc(amp->am_nextents); 173 amp->am_syncmap = bit_alloc(amp->am_nextents); 174 175 /* 176 * Check to see if any of the allocations above failed. 177 */ 178 if (amp->am_memtab == NULL || amp->am_diskmap == NULL || 179 amp->am_memmap == NULL || amp->am_syncmap == NULL) { 180 if (amp->am_memtab != NULL) 181 free(amp->am_memtab); 182 if (amp->am_diskmap != NULL) 183 free(amp->am_diskmap); 184 if (amp->am_memmap != NULL) 185 free(amp->am_memmap); 186 if (amp->am_syncmap != NULL) 187 free(amp->am_syncmap); 188 amp->am_magic = 0; 189 free(amp); 190 errno = ENOMEM; 191 return (-1); 192 } 193 194 amp->am_magic = ACTIVEMAP_MAGIC; 195 *ampp = amp; 196 197 return (0); 198 } 199 200 static struct keepdirty * 201 keepdirty_find(struct activemap *amp, int extent) 202 { 203 struct keepdirty *kd; 204 205 TAILQ_FOREACH(kd, &->am_keepdirty, kd_next) { 206 if (kd->kd_extent == extent) 207 break; 208 } 209 return (kd); 210 } 211 212 static bool 213 keepdirty_add(struct activemap *amp, int extent) 214 { 215 struct keepdirty *kd; 216 217 kd = keepdirty_find(amp, extent); 218 if (kd != NULL) { 219 /* 220 * Only move element at the beginning. 221 */ 222 TAILQ_REMOVE(&->am_keepdirty, kd, kd_next); 223 TAILQ_INSERT_HEAD(&->am_keepdirty, kd, kd_next); 224 return (false); 225 } 226 /* 227 * Add new element, but first remove the most unused one if 228 * we have too many. 229 */ 230 if (amp->am_nkeepdirty >= amp->am_nkeepdirty_limit) { 231 kd = TAILQ_LAST(&->am_keepdirty, skeepdirty); 232 PJDLOG_ASSERT(kd != NULL); 233 TAILQ_REMOVE(&->am_keepdirty, kd, kd_next); 234 amp->am_nkeepdirty--; 235 PJDLOG_ASSERT(amp->am_nkeepdirty > 0); 236 } 237 if (kd == NULL) 238 kd = malloc(sizeof(*kd)); 239 /* We can ignore allocation failure. */ 240 if (kd != NULL) { 241 kd->kd_extent = extent; 242 amp->am_nkeepdirty++; 243 TAILQ_INSERT_HEAD(&->am_keepdirty, kd, kd_next); 244 } 245 246 return (true); 247 } 248 249 static void 250 keepdirty_fill(struct activemap *amp) 251 { 252 struct keepdirty *kd; 253 254 TAILQ_FOREACH(kd, &->am_keepdirty, kd_next) 255 bit_set(amp->am_diskmap, kd->kd_extent); 256 } 257 258 static void 259 keepdirty_free(struct activemap *amp) 260 { 261 struct keepdirty *kd; 262 263 while ((kd = TAILQ_FIRST(&->am_keepdirty)) != NULL) { 264 TAILQ_REMOVE(&->am_keepdirty, kd, kd_next); 265 amp->am_nkeepdirty--; 266 free(kd); 267 } 268 PJDLOG_ASSERT(amp->am_nkeepdirty == 0); 269 } 270 271 /* 272 * Function frees resources allocated by activemap_init() function. 273 */ 274 void 275 activemap_free(struct activemap *amp) 276 { 277 278 PJDLOG_ASSERT(amp->am_magic == ACTIVEMAP_MAGIC); 279 280 amp->am_magic = 0; 281 282 keepdirty_free(amp); 283 free(amp->am_memtab); 284 free(amp->am_diskmap); 285 free(amp->am_memmap); 286 free(amp->am_syncmap); 287 } 288 289 /* 290 * Function should be called before we handle write requests. It updates 291 * internal structures and returns true if on-disk metadata should be updated. 292 */ 293 bool 294 activemap_write_start(struct activemap *amp, off_t offset, off_t length) 295 { 296 bool modified; 297 off_t end; 298 int ext; 299 300 PJDLOG_ASSERT(amp->am_magic == ACTIVEMAP_MAGIC); 301 PJDLOG_ASSERT(length > 0); 302 303 modified = false; 304 end = offset + length - 1; 305 306 for (ext = off2ext(amp, offset); ext <= off2ext(amp, end); ext++) { 307 /* 308 * If the number of pending writes is increased from 0, 309 * we have to mark the extent as dirty also in on-disk bitmap. 310 * By returning true we inform the caller that on-disk bitmap 311 * was modified and has to be flushed to disk. 312 */ 313 if (amp->am_memtab[ext]++ == 0) { 314 PJDLOG_ASSERT(!bit_test(amp->am_memmap, ext)); 315 bit_set(amp->am_memmap, ext); 316 amp->am_ndirty++; 317 } 318 if (keepdirty_add(amp, ext)) 319 modified = true; 320 } 321 322 return (modified); 323 } 324 325 /* 326 * Function should be called after receiving write confirmation. It updates 327 * internal structures and returns true if on-disk metadata should be updated. 328 */ 329 bool 330 activemap_write_complete(struct activemap *amp, off_t offset, off_t length) 331 { 332 bool modified; 333 off_t end; 334 int ext; 335 336 PJDLOG_ASSERT(amp->am_magic == ACTIVEMAP_MAGIC); 337 PJDLOG_ASSERT(length > 0); 338 339 modified = false; 340 end = offset + length - 1; 341 342 for (ext = off2ext(amp, offset); ext <= off2ext(amp, end); ext++) { 343 /* 344 * If the number of pending writes goes down to 0, we have to 345 * mark the extent as clean also in on-disk bitmap. 346 * By returning true we inform the caller that on-disk bitmap 347 * was modified and has to be flushed to disk. 348 */ 349 PJDLOG_ASSERT(amp->am_memtab[ext] > 0); 350 PJDLOG_ASSERT(bit_test(amp->am_memmap, ext)); 351 if (--amp->am_memtab[ext] == 0) { 352 bit_clear(amp->am_memmap, ext); 353 amp->am_ndirty--; 354 if (keepdirty_find(amp, ext) == NULL) 355 modified = true; 356 } 357 } 358 359 return (modified); 360 } 361 362 /* 363 * Function should be called after finishing synchronization of one extent. 364 * It returns true if on-disk metadata should be updated. 365 */ 366 bool 367 activemap_extent_complete(struct activemap *amp, int extent) 368 { 369 bool modified; 370 int reqs; 371 372 PJDLOG_ASSERT(amp->am_magic == ACTIVEMAP_MAGIC); 373 PJDLOG_ASSERT(extent >= 0 && extent < amp->am_nextents); 374 375 modified = false; 376 377 reqs = ext2reqs(amp, extent); 378 PJDLOG_ASSERT(amp->am_memtab[extent] >= reqs); 379 amp->am_memtab[extent] -= reqs; 380 PJDLOG_ASSERT(bit_test(amp->am_memmap, extent)); 381 if (amp->am_memtab[extent] == 0) { 382 bit_clear(amp->am_memmap, extent); 383 amp->am_ndirty--; 384 modified = true; 385 } 386 387 return (modified); 388 } 389 390 /* 391 * Function returns number of dirty regions. 392 */ 393 uint64_t 394 activemap_ndirty(const struct activemap *amp) 395 { 396 397 PJDLOG_ASSERT(amp->am_magic == ACTIVEMAP_MAGIC); 398 399 return (amp->am_ndirty); 400 } 401 402 /* 403 * Function compare on-disk bitmap and in-memory bitmap and returns true if 404 * they differ and should be flushed to the disk. 405 */ 406 bool 407 activemap_differ(const struct activemap *amp) 408 { 409 410 PJDLOG_ASSERT(amp->am_magic == ACTIVEMAP_MAGIC); 411 412 return (memcmp(amp->am_diskmap, amp->am_memmap, 413 amp->am_mapsize) != 0); 414 } 415 416 /* 417 * Function returns number of bytes used by bitmap. 418 */ 419 size_t 420 activemap_size(const struct activemap *amp) 421 { 422 423 PJDLOG_ASSERT(amp->am_magic == ACTIVEMAP_MAGIC); 424 425 return (amp->am_mapsize); 426 } 427 428 /* 429 * Function returns number of bytes needed for storing on-disk bitmap. 430 * This is the same as activemap_size(), but rounded up to sector size. 431 */ 432 size_t 433 activemap_ondisk_size(const struct activemap *amp) 434 { 435 436 PJDLOG_ASSERT(amp->am_magic == ACTIVEMAP_MAGIC); 437 438 return (amp->am_diskmapsize); 439 } 440 441 /* 442 * Function copies the given buffer read from disk to the internal bitmap. 443 */ 444 void 445 activemap_copyin(struct activemap *amp, const unsigned char *buf, size_t size) 446 { 447 int ext; 448 449 PJDLOG_ASSERT(amp->am_magic == ACTIVEMAP_MAGIC); 450 PJDLOG_ASSERT(size >= amp->am_mapsize); 451 452 memcpy(amp->am_diskmap, buf, amp->am_mapsize); 453 memcpy(amp->am_memmap, buf, amp->am_mapsize); 454 memcpy(amp->am_syncmap, buf, amp->am_mapsize); 455 456 bit_ffs(amp->am_memmap, amp->am_nextents, &ext); 457 if (ext == -1) { 458 /* There are no dirty extents, so we can leave now. */ 459 return; 460 } 461 /* 462 * Set synchronization offset to the first dirty extent. 463 */ 464 activemap_sync_rewind(amp); 465 /* 466 * We have dirty extents and we want them to stay that way until 467 * we synchronize, so we set number of pending writes to number 468 * of requests needed to synchronize one extent. 469 */ 470 amp->am_ndirty = 0; 471 for (; ext < amp->am_nextents; ext++) { 472 if (bit_test(amp->am_memmap, ext)) { 473 amp->am_memtab[ext] = ext2reqs(amp, ext); 474 amp->am_ndirty++; 475 } 476 } 477 } 478 479 /* 480 * Function merges the given bitmap with existing one. 481 */ 482 void 483 activemap_merge(struct activemap *amp, const unsigned char *buf, size_t size) 484 { 485 bitstr_t *remmap = __DECONST(bitstr_t *, buf); 486 int ext; 487 488 PJDLOG_ASSERT(amp->am_magic == ACTIVEMAP_MAGIC); 489 PJDLOG_ASSERT(size >= amp->am_mapsize); 490 491 bit_ffs(remmap, amp->am_nextents, &ext); 492 if (ext == -1) { 493 /* There are no dirty extents, so we can leave now. */ 494 return; 495 } 496 /* 497 * We have dirty extents and we want them to stay that way until 498 * we synchronize, so we set number of pending writes to number 499 * of requests needed to synchronize one extent. 500 */ 501 for (; ext < amp->am_nextents; ext++) { 502 /* Local extent already dirty. */ 503 if (bit_test(amp->am_syncmap, ext)) 504 continue; 505 /* Remote extent isn't dirty. */ 506 if (!bit_test(remmap, ext)) 507 continue; 508 bit_set(amp->am_syncmap, ext); 509 bit_set(amp->am_memmap, ext); 510 bit_set(amp->am_diskmap, ext); 511 if (amp->am_memtab[ext] == 0) 512 amp->am_ndirty++; 513 amp->am_memtab[ext] = ext2reqs(amp, ext); 514 } 515 /* 516 * Set synchronization offset to the first dirty extent. 517 */ 518 activemap_sync_rewind(amp); 519 } 520 521 /* 522 * Function returns pointer to internal bitmap that should be written to disk. 523 */ 524 const unsigned char * 525 activemap_bitmap(struct activemap *amp, size_t *sizep) 526 { 527 528 PJDLOG_ASSERT(amp->am_magic == ACTIVEMAP_MAGIC); 529 530 if (sizep != NULL) 531 *sizep = amp->am_diskmapsize; 532 memcpy(amp->am_diskmap, amp->am_memmap, amp->am_mapsize); 533 keepdirty_fill(amp); 534 return ((const unsigned char *)amp->am_diskmap); 535 } 536 537 /* 538 * Function calculates size needed to store bitmap on disk. 539 */ 540 size_t 541 activemap_calc_ondisk_size(uint64_t mediasize, uint32_t extentsize, 542 uint32_t sectorsize) 543 { 544 uint64_t nextents, mapsize; 545 546 PJDLOG_ASSERT(mediasize > 0); 547 PJDLOG_ASSERT(extentsize > 0); 548 PJDLOG_ASSERT(powerof2(extentsize)); 549 PJDLOG_ASSERT(sectorsize > 0); 550 PJDLOG_ASSERT(powerof2(sectorsize)); 551 552 nextents = ((mediasize - 1) / extentsize) + 1; 553 mapsize = bitstr_size(nextents); 554 return (roundup2(mapsize, sectorsize)); 555 } 556 557 /* 558 * Set synchronization offset to the first dirty extent. 559 */ 560 void 561 activemap_sync_rewind(struct activemap *amp) 562 { 563 int ext; 564 565 PJDLOG_ASSERT(amp->am_magic == ACTIVEMAP_MAGIC); 566 567 bit_ffs(amp->am_syncmap, amp->am_nextents, &ext); 568 if (ext == -1) { 569 /* There are no extents to synchronize. */ 570 amp->am_syncoff = -2; 571 return; 572 } 573 /* 574 * Mark that we want to start synchronization from the beginning. 575 */ 576 amp->am_syncoff = -1; 577 } 578 579 /* 580 * Return next offset of where we should synchronize. 581 */ 582 off_t 583 activemap_sync_offset(struct activemap *amp, off_t *lengthp, int *syncextp) 584 { 585 off_t syncoff, left; 586 int ext; 587 588 PJDLOG_ASSERT(amp->am_magic == ACTIVEMAP_MAGIC); 589 PJDLOG_ASSERT(lengthp != NULL); 590 PJDLOG_ASSERT(syncextp != NULL); 591 592 *syncextp = -1; 593 594 if (amp->am_syncoff == -2) 595 return (-1); 596 597 if (amp->am_syncoff >= 0 && 598 (amp->am_syncoff + MAXPHYS >= amp->am_mediasize || 599 off2ext(amp, amp->am_syncoff) != 600 off2ext(amp, amp->am_syncoff + MAXPHYS))) { 601 /* 602 * We are about to change extent, so mark previous one as clean. 603 */ 604 ext = off2ext(amp, amp->am_syncoff); 605 bit_clear(amp->am_syncmap, ext); 606 *syncextp = ext; 607 amp->am_syncoff = -1; 608 } 609 610 if (amp->am_syncoff == -1) { 611 /* 612 * Let's find first extent to synchronize. 613 */ 614 bit_ffs(amp->am_syncmap, amp->am_nextents, &ext); 615 if (ext == -1) { 616 amp->am_syncoff = -2; 617 return (-1); 618 } 619 amp->am_syncoff = ext2off(amp, ext); 620 } else { 621 /* 622 * We don't change extent, so just increase offset. 623 */ 624 amp->am_syncoff += MAXPHYS; 625 if (amp->am_syncoff >= amp->am_mediasize) { 626 amp->am_syncoff = -2; 627 return (-1); 628 } 629 } 630 631 syncoff = amp->am_syncoff; 632 left = ext2off(amp, off2ext(amp, syncoff)) + 633 amp->am_extentsize - syncoff; 634 if (syncoff + left > amp->am_mediasize) 635 left = amp->am_mediasize - syncoff; 636 if (left > MAXPHYS) 637 left = MAXPHYS; 638 639 PJDLOG_ASSERT(left >= 0 && left <= MAXPHYS); 640 PJDLOG_ASSERT(syncoff >= 0 && syncoff < amp->am_mediasize); 641 PJDLOG_ASSERT(syncoff + left >= 0 && 642 syncoff + left <= amp->am_mediasize); 643 644 *lengthp = left; 645 return (syncoff); 646 } 647 648 /* 649 * Mark extent(s) containing the given region for synchronization. 650 * Most likely one of the components is unavailable. 651 */ 652 bool 653 activemap_need_sync(struct activemap *amp, off_t offset, off_t length) 654 { 655 bool modified; 656 off_t end; 657 int ext; 658 659 PJDLOG_ASSERT(amp->am_magic == ACTIVEMAP_MAGIC); 660 661 modified = false; 662 end = offset + length - 1; 663 664 for (ext = off2ext(amp, offset); ext <= off2ext(amp, end); ext++) { 665 if (bit_test(amp->am_syncmap, ext)) { 666 /* Already marked for synchronization. */ 667 PJDLOG_ASSERT(bit_test(amp->am_memmap, ext)); 668 continue; 669 } 670 bit_set(amp->am_syncmap, ext); 671 if (!bit_test(amp->am_memmap, ext)) { 672 bit_set(amp->am_memmap, ext); 673 amp->am_ndirty++; 674 } 675 amp->am_memtab[ext] += ext2reqs(amp, ext); 676 modified = true; 677 } 678 679 return (modified); 680 } 681 682 void 683 activemap_dump(const struct activemap *amp) 684 { 685 int bit; 686 687 printf("M: "); 688 for (bit = 0; bit < amp->am_nextents; bit++) 689 printf("%d", bit_test(amp->am_memmap, bit) ? 1 : 0); 690 printf("\n"); 691 printf("D: "); 692 for (bit = 0; bit < amp->am_nextents; bit++) 693 printf("%d", bit_test(amp->am_diskmap, bit) ? 1 : 0); 694 printf("\n"); 695 printf("S: "); 696 for (bit = 0; bit < amp->am_nextents; bit++) 697 printf("%d", bit_test(amp->am_syncmap, bit) ? 1 : 0); 698 printf("\n"); 699 } 700