1 /* 2 * Copyright (c)2004 The DragonFly Project. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 8 * Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 11 * Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in 13 * the documentation and/or other materials provided with the 14 * distribution. 15 * 16 * Neither the name of the DragonFly Project nor the names of its 17 * contributors may be used to endorse or promote products derived 18 * from this software without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 23 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 24 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 25 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 26 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 29 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 30 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 31 * OF THE POSSIBILITY OF SUCH DAMAGE. 32 */ 33 34 /* 35 * diskutil.c 36 * Disk utility functions for installer. 37 * $Id: diskutil.c,v 1.44 2005/02/07 06:41:42 cpressey Exp $ 38 */ 39 40 #include <stdarg.h> 41 #include <stdio.h> 42 #include <stdlib.h> 43 #include <string.h> 44 45 #include "libaura/mem.h" 46 #include "libaura/fspred.h" 47 #include "libaura/popen.h" 48 49 #include "libdfui/dfui.h" 50 #include "libdfui/dump.h" 51 52 #define NEEDS_DISKUTIL_STRUCTURE_DEFINITIONS 53 #include "diskutil.h" 54 #undef NEEDS_DISKUTIL_STRUCTURE_DEFINITIONS 55 56 #include "commands.h" 57 #include "functions.h" 58 #include "sysids.h" 59 #include "uiutil.h" 60 61 static int disk_description_is_better(const char *, const char *); 62 63 /** STORAGE DESCRIPTORS **/ 64 65 struct storage * 66 storage_new(void) 67 { 68 struct storage *s; 69 70 AURA_MALLOC(s, storage); 71 72 s->disk_head = NULL; 73 s->disk_tail = NULL; 74 s->selected_disk = NULL; 75 s->selected_slice = NULL; 76 s->ram = -1; 77 78 return(s); 79 } 80 81 int 82 storage_get_mfs_status(const char *mountpoint, struct storage *s) 83 { 84 struct subpartition *sp; 85 sp = NULL; 86 for (sp = slice_subpartition_first(s->selected_slice); 87 sp != NULL; sp = subpartition_next(sp)) { 88 if(strcmp(subpartition_get_mountpoint(sp), mountpoint) == 0) { 89 if(subpartition_is_mfsbacked(sp) == 1) { 90 return 1; 91 } else { 92 return 0; 93 } 94 } 95 } 96 return 0; 97 } 98 99 void 100 storage_free(struct storage *s) 101 { 102 disks_free(s); 103 AURA_FREE(s, storage); 104 } 105 106 void 107 storage_set_memsize(struct storage *s, unsigned long memsize) 108 { 109 s->ram = memsize; 110 } 111 112 unsigned long 113 storage_get_memsize(const struct storage *s) 114 { 115 return(s->ram); 116 } 117 118 struct disk * 119 storage_disk_first(const struct storage *s) 120 { 121 return(s->disk_head); 122 } 123 124 void 125 storage_set_selected_disk(struct storage *s, struct disk *d) 126 { 127 s->selected_disk = d; 128 } 129 130 struct disk * 131 storage_get_selected_disk(const struct storage *s) 132 { 133 return(s->selected_disk); 134 } 135 136 void 137 storage_set_selected_slice(struct storage *s, struct slice *sl) 138 { 139 s->selected_slice = sl; 140 } 141 142 struct slice * 143 storage_get_selected_slice(const struct storage *s) 144 { 145 return(s->selected_slice); 146 } 147 148 /* 149 * Create a new disk description structure. 150 */ 151 struct disk * 152 disk_new(struct storage *s, const char *dev_name) 153 { 154 struct disk *d; 155 156 if (disk_find(s, dev_name) != NULL) { 157 /* Already discovered */ 158 return(NULL); 159 } 160 161 AURA_MALLOC(d, disk); 162 163 d->device = aura_strdup(dev_name); 164 d->desc = NULL; 165 d->we_formatted = 0; 166 d->capacity = 0; 167 168 d->cylinders = -1; /* -1 indicates "we don't know" */ 169 d->heads = -1; 170 d->sectors = -1; 171 172 d->slice_head = NULL; 173 d->slice_tail = NULL; 174 175 d->next = NULL; 176 if (s->disk_head == NULL) 177 s->disk_head = d; 178 else 179 s->disk_tail->next = d; 180 181 d->prev = s->disk_tail; 182 s->disk_tail = d; 183 184 return(d); 185 } 186 187 static int 188 disk_description_is_better(const char *existing, const char *new_desc __unused) 189 { 190 if (existing == NULL) 191 return(1); 192 return(0); 193 } 194 195 const char * 196 disk_get_desc(const struct disk *d) 197 { 198 return(d->desc); 199 } 200 201 void 202 disk_set_desc(struct disk *d, const char *desc) 203 { 204 char *c; 205 206 if (!disk_description_is_better(d->desc, desc)) 207 return; 208 if (d->desc != NULL) 209 free(d->desc); 210 d->desc = aura_strdup(desc); 211 212 /* 213 * Get the disk's total capacity. 214 * XXX we should do this with C/H/S ? 215 */ 216 c = d->desc; 217 while (*c != ':' && *c != '\0') 218 c++; 219 if (*c == '\0') 220 d->capacity = 0; 221 else 222 d->capacity = atoi(c + 1); 223 } 224 225 /* 226 * Returns the name of the device node used to represent the disk. 227 * Note that the storage used for the returned string is static, 228 * and the string is overwritten each time this function is called. 229 */ 230 const char * 231 disk_get_device_name(const struct disk *d) 232 { 233 static char tmp_dev_name[256]; 234 235 snprintf(tmp_dev_name, 256, "%s", d->device); 236 return(tmp_dev_name); 237 } 238 239 /* 240 * Returns the name of the device node used to represent the 241 * raw disk device. 242 * Note that the storage used for the returned string is static, 243 * and the string is overwritten each time this function is called. 244 */ 245 const char * 246 disk_get_raw_device_name(const struct disk *d) 247 { 248 static char tmp_dev_name[256]; 249 250 snprintf(tmp_dev_name, 256, "%s", d->device); 251 return(tmp_dev_name); 252 } 253 254 /* 255 * Find the first disk description structure in the given 256 * storage description which matches the given device name 257 * prefix. Note that this means that if a storage 258 * description s contains disks named "ad0" and "ad1", 259 * disk_find(s, "ad0s1c") will return a pointer to the disk 260 * structure for "ad0". 261 */ 262 struct disk * 263 disk_find(const struct storage *s, const char *device) 264 { 265 struct disk *d = s->disk_head; 266 267 while (d != NULL) { 268 if (strncmp(device, d->device, strlen(d->device)) == 0) 269 return(d); 270 d = d->next; 271 } 272 273 return(NULL); 274 } 275 276 struct disk * 277 disk_next(const struct disk *d) 278 { 279 return(d->next); 280 } 281 282 struct slice * 283 disk_slice_first(const struct disk *d) 284 { 285 return(d->slice_head); 286 } 287 288 void 289 disk_set_formatted(struct disk *d, int formatted) 290 { 291 d->we_formatted = formatted; 292 } 293 294 int 295 disk_get_formatted(const struct disk *d) 296 { 297 return(d->we_formatted); 298 } 299 300 void 301 disk_set_geometry(struct disk *d, int cyl, int hd, int sec) 302 { 303 d->cylinders = cyl; 304 d->heads = hd; 305 d->sectors = sec; 306 } 307 308 void 309 disk_get_geometry(const struct disk *d, int *cyl, int *hd, int *sec) 310 { 311 *cyl = d->cylinders; 312 *hd = d->heads; 313 *sec = d->sectors; 314 } 315 316 /* 317 * Free the memory allocated to hold the set of disk descriptions. 318 */ 319 void 320 disks_free(struct storage *s) 321 { 322 struct disk *d = s->disk_head, *next; 323 324 while (d != NULL) { 325 next = d->next; 326 slices_free(d->slice_head); 327 free(d->desc); 328 free(d->device); 329 AURA_FREE(d, disk); 330 d = next; 331 } 332 333 s->disk_head = NULL; 334 s->disk_tail = NULL; 335 } 336 337 /* 338 * Create a new slice description and add it to a disk description. 339 */ 340 struct slice * 341 slice_new(struct disk *d, int number, int type, int flags, 342 unsigned long start, unsigned long size) 343 { 344 struct slice *s; 345 const char *sysid_desc = NULL; 346 char unknown[256]; 347 int i; 348 349 dfui_debug("** adding slice %d (start %ld, size %ld, sysid %d) " 350 "to disk %s\n", number, start, size, type, d->device); 351 352 AURA_MALLOC(s, slice); 353 354 s->parent = d; 355 356 s->subpartition_head = NULL; 357 s->subpartition_tail = NULL; 358 s->number = number; 359 360 s->type = type; 361 s->flags = flags; 362 s->start = start; 363 s->size = size; 364 365 for (i = 0; ; i++) { 366 if (part_types[i].type == type) { 367 sysid_desc = part_types[i].name; 368 break; 369 } 370 if (part_types[i].type == 255) 371 break; 372 } 373 if (sysid_desc == NULL) { 374 snprintf(unknown, 256, "??? Unknown, sysid = %d", type); 375 sysid_desc = unknown; 376 } 377 378 asprintf(&s->desc, "%ldM - %ldM: %s", 379 start / 2048, (start + size) / 2048, sysid_desc); 380 s->capacity = size / 2048; 381 382 s->next = NULL; 383 if (d->slice_head == NULL) 384 d->slice_head = s; 385 else 386 d->slice_tail->next = s; 387 388 s->prev = d->slice_tail; 389 d->slice_tail = s; 390 391 return(s); 392 } 393 394 /* 395 * Find a slice description on a given disk description given the 396 * slice number. 397 */ 398 struct slice * 399 slice_find(const struct disk *d, int number) 400 { 401 struct slice *s = d->slice_head; 402 403 while (s != NULL) { 404 if (s->number == number) 405 return(s); 406 s = s->next; 407 } 408 409 return(NULL); 410 } 411 412 struct slice * 413 slice_next(const struct slice *s) 414 { 415 return(s->next); 416 } 417 418 /* 419 * Returns the name of the device node used to represent the slice. 420 * Note that the storage used for the returned string is static, 421 * and the string is overwritten each time this function is called. 422 */ 423 const char * 424 slice_get_device_name(const struct slice *s) 425 { 426 static char tmp_dev_name[256]; 427 428 snprintf(tmp_dev_name, 256, "%ss%d", s->parent->device, s->number); 429 return(tmp_dev_name); 430 } 431 432 /* 433 * Returns the name of the device node used to represent 434 * the raw slice. 435 * Note that the storage used for the returned string is static, 436 * and the string is overwritten each time this function is called. 437 */ 438 const char * 439 slice_get_raw_device_name(const struct slice *s) 440 { 441 static char tmp_dev_name[256]; 442 443 snprintf(tmp_dev_name, 256, "%ss%d", s->parent->device, s->number); 444 return(tmp_dev_name); 445 } 446 447 int 448 slice_get_number(const struct slice *s) 449 { 450 return(s->number); 451 } 452 453 const char * 454 slice_get_desc(const struct slice *s) 455 { 456 return(s->desc); 457 } 458 459 unsigned long 460 slice_get_capacity(const struct slice *s) 461 { 462 return(s->capacity); 463 } 464 465 unsigned long 466 slice_get_start(const struct slice *s) 467 { 468 return(s->start); 469 } 470 471 unsigned long 472 slice_get_size(const struct slice *s) 473 { 474 return(s->size); 475 } 476 477 int 478 slice_get_type(const struct slice *s) 479 { 480 return(s->type); 481 } 482 483 int 484 slice_get_flags(const struct slice *s) 485 { 486 return(s->flags); 487 } 488 489 struct subpartition * 490 slice_subpartition_first(const struct slice *s) 491 { 492 return(s->subpartition_head); 493 } 494 495 /* 496 * Free all memory for a list of slice descriptions. 497 */ 498 void 499 slices_free(struct slice *head) 500 { 501 struct slice *next; 502 503 while (head != NULL) { 504 next = head->next; 505 subpartitions_free(head); 506 free(head->desc); 507 AURA_FREE(head, slice); 508 head = next; 509 } 510 } 511 512 struct subpartition * 513 subpartition_new_hammer(struct slice *s, const char *mountpoint, long capacity) 514 { 515 struct subpartition *sp; 516 517 AURA_MALLOC(sp, subpartition); 518 519 sp->parent = s; 520 521 struct subpartition *last = s->subpartition_tail; 522 if (last == NULL) { 523 sp->letter = 'a'; 524 } else if (last->letter == 'b') { 525 sp->letter = 'd'; 526 } else { 527 sp->letter = (char)(last->letter + 1); 528 } 529 530 sp->mountpoint = aura_strdup(mountpoint); 531 sp->capacity = capacity; 532 sp->type = FS_HAMMER; 533 534 /* 535 * We need this here, because a UFS /boot needs valid values 536 */ 537 if (sp->capacity < 1024) 538 sp->fsize = 1024; 539 else 540 sp->fsize = 2048; 541 542 if (sp->capacity < 1024) 543 sp->bsize = 8192; 544 else 545 sp->bsize = 16384; 546 547 sp->is_swap = 0; 548 sp->pfs = 0; 549 if (strcasecmp(mountpoint, "swap") == 0) 550 sp->is_swap = 1; 551 if (strcmp(mountpoint, "/") != 0 && strcmp(mountpoint, "/boot") != 0 && 552 strcmp(mountpoint, "swap") != 0) 553 sp->pfs = 1; 554 555 sp->next = NULL; 556 if (s->subpartition_head == NULL) 557 s->subpartition_head = sp; 558 else 559 s->subpartition_tail->next = sp; 560 561 sp->prev = s->subpartition_tail; 562 s->subpartition_tail = sp; 563 564 return(sp); 565 } 566 567 /* 568 * NOTE: arguments to this function are not checked for sanity. 569 * 570 * fsize and/or bsize may both be -1, indicating 571 * "choose a reasonable default." 572 */ 573 struct subpartition * 574 subpartition_new(struct slice *s, const char *mountpoint, long capacity, 575 int softupdates, long fsize, long bsize, int mfsbacked) 576 { 577 struct subpartition *sp, *sptmp; 578 int letter='d'; 579 580 AURA_MALLOC(sp, subpartition); 581 582 sp->parent = s; 583 584 sp->mountpoint = aura_strdup(mountpoint); 585 sp->capacity = capacity; 586 sp->type = FS_UFS; 587 588 if (fsize == -1) { 589 if (sp->capacity < 1024) 590 sp->fsize = 1024; 591 else 592 sp->fsize = 2048; 593 } else { 594 sp->fsize = fsize; 595 } 596 597 if (bsize == -1) { 598 if (sp->capacity < 1024) 599 sp->bsize = 8192; 600 else 601 sp->bsize = 16384; 602 } else { 603 sp->bsize = bsize; 604 } 605 606 if (softupdates == -1) { 607 if (strcmp(mountpoint, "/") == 0) 608 sp->softupdates = 0; 609 else 610 sp->softupdates = 1; 611 } else { 612 sp->softupdates = softupdates; 613 } 614 615 sp->mfsbacked = mfsbacked; 616 617 sp->is_swap = 0; 618 if (strcasecmp(mountpoint, "swap") == 0) 619 sp->is_swap = 1; 620 621 if (s->subpartition_head == NULL) { 622 s->subpartition_head = sp; 623 s->subpartition_tail = sp; 624 } else { 625 for (sptmp = s->subpartition_head; sptmp != NULL; 626 sptmp = sptmp->next) { 627 if (strcmp(sptmp->mountpoint, sp->mountpoint) > 0) 628 break; 629 } 630 if (sptmp != NULL) { 631 if (s->subpartition_head == sptmp) 632 s->subpartition_head = sp; 633 else 634 sptmp->prev->next = sp; 635 sp->next = sptmp; 636 sp->prev = sptmp->prev; 637 sptmp->prev = sp; 638 } else { 639 sp->prev = s->subpartition_tail; 640 s->subpartition_tail->next = sp; 641 s->subpartition_tail = sp; 642 } 643 } 644 645 for (sptmp = s->subpartition_head; sptmp != NULL; 646 sptmp = sptmp->next) { 647 if (sptmp->mfsbacked) 648 sptmp->letter = '@'; 649 else if (strcmp(sptmp->mountpoint, "/") == 0 || 650 strcmp(sptmp->mountpoint, "/dummy") == 0) 651 sptmp->letter = 'a'; 652 else if (strcasecmp(sptmp->mountpoint, "swap") == 0) 653 sptmp->letter = 'b'; 654 else 655 sptmp->letter = letter++; 656 } 657 658 return(sp); 659 } 660 661 /* 662 * Find the subpartition description in the given storage 663 * description whose mountpoint matches the given string exactly. 664 */ 665 struct subpartition * 666 subpartition_find(const struct slice *s, const char *fmt, ...) 667 { 668 struct subpartition *sp = s->subpartition_head; 669 char *mountpoint; 670 va_list args; 671 672 va_start(args, fmt); 673 vasprintf(&mountpoint, fmt, args); 674 va_end(args); 675 676 while (sp != NULL) { 677 if (strcmp(mountpoint, sp->mountpoint) == 0) { 678 free(mountpoint); 679 return(sp); 680 } 681 sp = sp->next; 682 } 683 684 free(mountpoint); 685 return(NULL); 686 } 687 688 /* 689 * Find the subpartition description in the given storage 690 * description where the given filename would presumably 691 * reside. This is the subpartition whose mountpoint is 692 * the longest match for the given filename. 693 */ 694 struct subpartition * 695 subpartition_of(const struct slice *s, const char *fmt, ...) 696 { 697 struct subpartition *sp = s->subpartition_head; 698 struct subpartition *csp = NULL; 699 size_t len = 0; 700 char *filename; 701 va_list args; 702 703 va_start(args, fmt); 704 vasprintf(&filename, fmt, args); 705 va_end(args); 706 707 while (sp != NULL) { 708 if (strlen(sp->mountpoint) > len && 709 strlen(sp->mountpoint) <= strlen(filename) && 710 strncmp(filename, sp->mountpoint, strlen(sp->mountpoint)) == 0) { 711 csp = sp; 712 len = strlen(csp->mountpoint); 713 } 714 sp = sp->next; 715 } 716 717 free(filename); 718 return(csp); 719 } 720 721 struct subpartition * 722 subpartition_find_capacity(const struct slice *s, long capacity) 723 { 724 struct subpartition *sp = s->subpartition_head; 725 726 while (sp != NULL) { 727 if (sp->capacity == capacity) 728 return(sp); 729 sp = sp->next; 730 } 731 732 return(NULL); 733 } 734 735 struct subpartition * 736 subpartition_next(const struct subpartition *sp) 737 { 738 return(sp->next); 739 } 740 741 int 742 subpartition_get_pfs(const struct subpartition *sp) 743 { 744 return(sp->pfs); 745 } 746 747 /* 748 * Returns the name of the device node used to represent 749 * the subpartition. 750 * Note that the storage used for the returned string is static, 751 * and the string is overwritten each time this function is called. 752 */ 753 const char * 754 subpartition_get_device_name(const struct subpartition *sp) 755 { 756 static char tmp_dev_name[256]; 757 758 snprintf(tmp_dev_name, 256, "%ss%d%c", sp->parent->parent->device, 759 sp->parent->number, sp->letter); 760 return(tmp_dev_name); 761 } 762 763 /* 764 * Returns the name of the device node used to represent 765 * the raw subpartition. 766 * Note that the storage used for the returned string is static, 767 * and the string is overwritten each time this function is called. 768 */ 769 const char * 770 subpartition_get_raw_device_name(const struct subpartition *sp) 771 { 772 static char tmp_dev_name[256]; 773 774 snprintf(tmp_dev_name, 256, "r%ss%d%c", sp->parent->parent->device, 775 sp->parent->number, sp->letter); 776 return(tmp_dev_name); 777 } 778 779 const char * 780 subpartition_get_mountpoint(const struct subpartition *sp) 781 { 782 return(sp->mountpoint); 783 } 784 785 char 786 subpartition_get_letter(const struct subpartition *sp) 787 { 788 return(sp->letter); 789 } 790 791 unsigned long 792 subpartition_get_fsize(const struct subpartition *sp) 793 { 794 return(sp->fsize); 795 } 796 797 unsigned long 798 subpartition_get_bsize(const struct subpartition *sp) 799 { 800 return(sp->bsize); 801 } 802 803 unsigned long 804 subpartition_get_capacity(const struct subpartition *sp) 805 { 806 return(sp->capacity); 807 } 808 809 int 810 subpartition_is_swap(const struct subpartition *sp) 811 { 812 return(sp->is_swap); 813 } 814 815 int 816 subpartition_is_softupdated(const struct subpartition *sp) 817 { 818 return(sp->softupdates); 819 } 820 int 821 subpartition_is_mfsbacked(const struct subpartition *sp) 822 { 823 return(sp->mfsbacked); 824 } 825 826 int 827 subpartition_count(const struct slice *s) 828 { 829 struct subpartition *sp = s->subpartition_head; 830 int count = 0; 831 832 while (sp != NULL) { 833 count++; 834 sp = sp->next; 835 } 836 837 return(count); 838 } 839 840 void 841 subpartitions_free(struct slice *s) 842 { 843 struct subpartition *sp = s->subpartition_head, *next; 844 845 while (sp != NULL) { 846 next = sp->next; 847 free(sp->mountpoint); 848 AURA_FREE(sp, subpartition); 849 sp = next; 850 } 851 852 s->subpartition_head = NULL; 853 s->subpartition_tail = NULL; 854 } 855 856 long 857 measure_activated_swap(const struct i_fn_args *a) 858 { 859 FILE *p; 860 char line[256]; 861 char *word; 862 long swap = 0; 863 864 if ((p = aura_popen("%s%s -k", "r", a->os_root, cmd_name(a, "SWAPINFO"))) == NULL) 865 return(0); 866 while (fgets(line, 255, p) != NULL) { 867 if ((word = strtok(line, " \t")) == NULL) 868 continue; 869 if (strcmp(word, "Device") == 0) 870 continue; 871 if ((word = strtok(NULL, " \t")) == NULL) 872 continue; 873 swap += atol(word); 874 } 875 aura_pclose(p); 876 877 return(swap / 1024); 878 } 879 880 long 881 measure_activated_swap_from_slice(const struct i_fn_args *a, 882 const struct disk *d, const struct slice *s) 883 { 884 FILE *p; 885 char *dev, *word; 886 char line[256]; 887 long swap = 0; 888 889 if ((p = aura_popen("%s%s -k", "r", a->os_root, cmd_name(a, "SWAPINFO"))) == NULL) 890 return(0); 891 892 asprintf(&dev, "/dev/%ss%d", d->device, s->number); 893 894 while (fgets(line, 255, p) != NULL) { 895 if ((word = strtok(line, " \t")) == NULL) 896 continue; 897 if (strcmp(word, "Device") == 0) 898 continue; 899 if (strstr(word, dev) != word) 900 continue; 901 if ((word = strtok(NULL, " \t")) == NULL) 902 continue; 903 swap += atol(word); 904 } 905 aura_pclose(p); 906 free(dev); 907 908 return(swap / 1024); 909 } 910 911 long 912 measure_activated_swap_from_disk(const struct i_fn_args *a, 913 const struct disk *d) 914 { 915 struct slice *s; 916 long swap = 0; 917 918 for (s = d->slice_head; s != NULL; s = s->next) 919 swap += measure_activated_swap_from_slice(a, d, s); 920 921 return(swap); 922 } 923