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