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->serno = NULL;
166 	d->we_formatted = 0;
167 	d->capacity = 0;
168 
169 	d->cylinders = -1;	/* -1 indicates "we don't know" */
170 	d->heads = -1;
171 	d->sectors = -1;
172 
173 	d->slice_head = NULL;
174 	d->slice_tail = NULL;
175 
176 	d->next = NULL;
177 	if (s->disk_head == NULL)
178 		s->disk_head = d;
179 	else
180 		s->disk_tail->next = d;
181 
182 	d->prev = s->disk_tail;
183 	s->disk_tail = d;
184 
185 	return(d);
186 }
187 
188 static int
189 disk_description_is_better(const char *existing, const char *new_desc __unused)
190 {
191 	if (existing == NULL)
192 		return(1);
193 	return(0);
194 }
195 
196 const char *
197 disk_get_desc(const struct disk *d)
198 {
199 	return(d->desc);
200 }
201 
202 void
203 disk_set_desc(struct disk *d, const char *desc)
204 {
205 	char *c;
206 
207 	if (!disk_description_is_better(d->desc, desc))
208 		return;
209 	if (d->desc != NULL)
210 		free(d->desc);
211 	d->desc = aura_strdup(desc);
212 
213 	/*
214 	 * Get the disk's total capacity.
215 	 * XXX we should do this with C/H/S ?
216 	 */
217 	c = d->desc;
218 	while (*c != ':' && *c != '\0')
219 		c++;
220 	if (*c == '\0')
221 		d->capacity = 0;
222 	else
223 		d->capacity = atoi(c + 1);
224 }
225 
226 /*
227  * Returns the name of the device node used to represent the disk.
228  * Note that the storage used for the returned string is static,
229  * and the string is overwritten each time this function is called.
230  */
231 const char *
232 disk_get_device_name(const struct disk *d)
233 {
234 	static char tmp_dev_name[256];
235 
236 	snprintf(tmp_dev_name, 256, "%s", d->device);
237 	return(tmp_dev_name);
238 }
239 
240 const char *
241 disk_get_serno(const struct disk *d)
242 {
243 	return(d->serno);
244 }
245 
246 void
247 disk_set_serno(struct disk *d, const char *serno)
248 {
249 	d->serno = aura_strdup(serno);
250 }
251 
252 int
253 disk_get_number(const struct disk *d)
254 {
255 	return(d->number);
256 }
257 
258 void
259 disk_set_number(struct disk *d, const int number)
260 {
261 	d->number = number;
262 }
263 
264 /*
265  * Find the first disk description structure in the given
266  * storage description which matches the given device name
267  * prefix.  Note that this means that if a storage
268  * description s contains disks named "ad0" and "ad1",
269  * disk_find(s, "ad0s1c") will return a pointer to the disk
270  * structure for "ad0".
271  */
272 struct disk *
273 disk_find(const struct storage *s, const char *device)
274 {
275 	struct disk *d = s->disk_head;
276 
277 	while (d != NULL) {
278 		if (strncmp(device, d->device, strlen(d->device)) == 0)
279 			return(d);
280 		d = d->next;
281 	}
282 
283 	return(NULL);
284 }
285 
286 struct disk *
287 disk_next(const struct disk *d)
288 {
289 	return(d->next);
290 }
291 
292 struct slice *
293 disk_slice_first(const struct disk *d)
294 {
295 	return(d->slice_head);
296 }
297 
298 void
299 disk_set_formatted(struct disk *d, int formatted)
300 {
301 	d->we_formatted = formatted;
302 }
303 
304 int
305 disk_get_formatted(const struct disk *d)
306 {
307 	return(d->we_formatted);
308 }
309 
310 void
311 disk_set_geometry(struct disk *d, int cyl, int hd, int sec)
312 {
313 	d->cylinders = cyl;
314 	d->heads = hd;
315 	d->sectors = sec;
316 }
317 
318 void
319 disk_get_geometry(const struct disk *d, int *cyl, int *hd, int *sec)
320 {
321 	*cyl = d->cylinders;
322 	*hd = d->heads;
323 	*sec = d->sectors;
324 }
325 
326 /*
327  * Free the memory allocated to hold the set of disk descriptions.
328  */
329 void
330 disks_free(struct storage *s)
331 {
332 	struct disk *d = s->disk_head, *next;
333 
334 	while (d != NULL) {
335 		next = d->next;
336 		slices_free(d->slice_head);
337 		free(d->desc);
338 		free(d->device);
339 		AURA_FREE(d, disk);
340 		d = next;
341 	}
342 
343 	s->disk_head = NULL;
344 	s->disk_tail = NULL;
345 }
346 
347 /*
348  * Create a new slice description and add it to a disk description.
349  */
350 struct slice *
351 slice_new(struct disk *d, int number, int type, int flags,
352 	  unsigned long start, unsigned long size)
353 {
354 	struct slice *s;
355 	const char *sysid_desc = NULL;
356 	char unknown[256];
357 	int i;
358 
359 	dfui_debug("** adding slice %d (start %ld, size %ld, sysid %d) "
360 	    "to disk %s\n", number, start, size, type, d->device);
361 
362 	AURA_MALLOC(s, slice);
363 
364 	s->parent = d;
365 
366 	s->subpartition_head = NULL;
367 	s->subpartition_tail = NULL;
368 	s->number = number;
369 
370 	s->type = type;
371 	s->flags = flags;
372 	s->start = start;
373 	s->size = size;
374 
375 	for (i = 0; ; i++) {
376 		if (part_types[i].type == type) {
377 			sysid_desc = part_types[i].name;
378 			break;
379 		}
380 		if (part_types[i].type == 255)
381 			break;
382 	}
383 	if (sysid_desc == NULL) {
384 		snprintf(unknown, 256, "??? Unknown, sysid = %d", type);
385 		sysid_desc = unknown;
386 	}
387 
388 	asprintf(&s->desc, "%ldM - %ldM: %s",
389 	    start / 2048, (start + size) / 2048, sysid_desc);
390 	s->capacity = size / 2048;
391 
392 	s->next = NULL;
393 	if (d->slice_head == NULL)
394 		d->slice_head = s;
395 	else
396 		d->slice_tail->next = s;
397 
398 	s->prev = d->slice_tail;
399 	d->slice_tail = s;
400 
401 	return(s);
402 }
403 
404 /*
405  * Find a slice description on a given disk description given the
406  * slice number.
407  */
408 struct slice *
409 slice_find(const struct disk *d, int number)
410 {
411 	struct slice *s = d->slice_head;
412 
413 	while (s != NULL) {
414 		if (s->number == number)
415 			return(s);
416 		s = s->next;
417 	}
418 
419 	return(NULL);
420 }
421 
422 struct slice *
423 slice_next(const struct slice *s)
424 {
425 	return(s->next);
426 }
427 
428 /*
429  * Returns the name of the device node used to represent the slice.
430  * Note that the storage used for the returned string is static,
431  * and the string is overwritten each time this function is called.
432  */
433 const char *
434 slice_get_device_name(const struct slice *s)
435 {
436 	static char tmp_dev_name[256];
437 
438 	snprintf(tmp_dev_name, 256, "%ss%d", s->parent->device, s->number);
439 	return(tmp_dev_name);
440 }
441 
442 int
443 slice_get_number(const struct slice *s)
444 {
445 	return(s->number);
446 }
447 
448 const char *
449 slice_get_desc(const struct slice *s)
450 {
451 	return(s->desc);
452 }
453 
454 unsigned long
455 slice_get_capacity(const struct slice *s)
456 {
457 	return(s->capacity);
458 }
459 
460 unsigned long
461 slice_get_start(const struct slice *s)
462 {
463 	return(s->start);
464 }
465 
466 unsigned long
467 slice_get_size(const struct slice *s)
468 {
469 	return(s->size);
470 }
471 
472 int
473 slice_get_type(const struct slice *s)
474 {
475 	return(s->type);
476 }
477 
478 int
479 slice_get_flags(const struct slice *s)
480 {
481 	return(s->flags);
482 }
483 
484 struct subpartition *
485 slice_subpartition_first(const struct slice *s)
486 {
487 	return(s->subpartition_head);
488 }
489 
490 /*
491  * Free all memory for a list of slice descriptions.
492  */
493 void
494 slices_free(struct slice *head)
495 {
496 	struct slice *next;
497 
498 	while (head != NULL) {
499 		next = head->next;
500 		subpartitions_free(head);
501 		free(head->desc);
502 		AURA_FREE(head, slice);
503 		head = next;
504 	}
505 }
506 
507 struct subpartition *
508 subpartition_new_hammer(struct slice *s, const char *mountpoint, long capacity)
509 {
510 	struct subpartition *sp;
511 
512 	AURA_MALLOC(sp, subpartition);
513 
514 	sp->parent = s;
515 
516 	struct subpartition *last = s->subpartition_tail;
517 	if (last == NULL) {
518 		sp->letter = 'a';
519 	} else if (last->letter == 'b') {
520 		sp->letter = 'd';
521 	} else {
522 		sp->letter = (char)(last->letter + 1);
523 	}
524 
525 	sp->mountpoint = aura_strdup(mountpoint);
526 	sp->capacity = capacity;
527 	sp->type = FS_HAMMER;
528 
529 	/*
530 	 * We need this here, because a UFS /boot needs valid values
531 	 */
532 	if (sp->capacity < 1024)
533 		sp->fsize = 1024;
534 	else
535 		sp->fsize = 2048;
536 
537 	if (sp->capacity < 1024)
538 		sp->bsize = 8192;
539 	else
540 		sp->bsize = 16384;
541 
542 	sp->is_swap = 0;
543 	sp->pfs = 0;
544 	if (strcasecmp(mountpoint, "swap") == 0)
545 		sp->is_swap = 1;
546 	if (strcmp(mountpoint, "/") != 0 && strcmp(mountpoint, "/boot") != 0 &&
547 	    strcmp(mountpoint, "swap") != 0)
548 		sp->pfs = 1;
549 
550 	sp->next = NULL;
551 	if (s->subpartition_head == NULL)
552 		s->subpartition_head = sp;
553 	else
554 		s->subpartition_tail->next = sp;
555 
556 	sp->prev = s->subpartition_tail;
557 	s->subpartition_tail = sp;
558 
559 	return(sp);
560 }
561 
562 /*
563  * NOTE: arguments to this function are not checked for sanity.
564  *
565  * fsize and/or bsize may both be -1, indicating
566  * "choose a reasonable default."
567  */
568 struct subpartition *
569 subpartition_new(struct slice *s, const char *mountpoint, long capacity,
570 		 int softupdates, long fsize, long bsize, int mfsbacked)
571 {
572 	struct subpartition *sp, *sptmp;
573 	int letter='d';
574 
575 	AURA_MALLOC(sp, subpartition);
576 
577 	sp->parent = s;
578 
579 	sp->mountpoint = aura_strdup(mountpoint);
580 	sp->capacity = capacity;
581 	sp->type = FS_UFS;
582 
583 	if (fsize == -1) {
584 		if (sp->capacity < 1024)
585 			sp->fsize = 1024;
586 		else
587 			sp->fsize = 2048;
588 	} else {
589 		sp->fsize = fsize;
590 	}
591 
592 	if (bsize == -1) {
593 		if (sp->capacity < 1024)
594 			sp->bsize = 8192;
595 		else
596 			sp->bsize = 16384;
597 	} else {
598 		sp->bsize = bsize;
599 	}
600 
601 	if (softupdates == -1) {
602 		if (strcmp(mountpoint, "/") == 0)
603 			sp->softupdates = 0;
604 		else
605 			sp->softupdates = 1;
606 	} else {
607 		sp->softupdates = softupdates;
608 	}
609 
610 	sp->mfsbacked = mfsbacked;
611 
612 	sp->is_swap = 0;
613 	if (strcasecmp(mountpoint, "swap") == 0)
614 		sp->is_swap = 1;
615 
616 	if (s->subpartition_head == NULL) {
617 		s->subpartition_head = sp;
618 		s->subpartition_tail = sp;
619 	} else {
620 		for (sptmp = s->subpartition_head; sptmp != NULL;
621 		     sptmp = sptmp->next) {
622 			if (strcmp(sptmp->mountpoint, sp->mountpoint) > 0)
623 				break;
624 		}
625 		if (sptmp != NULL) {
626 			if (s->subpartition_head == sptmp)
627 				s->subpartition_head = sp;
628 			else
629 				sptmp->prev->next = sp;
630 			sp->next = sptmp;
631 			sp->prev = sptmp->prev;
632 			sptmp->prev = sp;
633 		} else {
634 			sp->prev = s->subpartition_tail;
635 			s->subpartition_tail->next = sp;
636 			s->subpartition_tail = sp;
637 		}
638 	}
639 
640 	for (sptmp = s->subpartition_head; sptmp != NULL;
641 	     sptmp = sptmp->next) {
642 		if (sptmp->mfsbacked)
643 			sptmp->letter = '@';
644 		else if (strcmp(sptmp->mountpoint, "/") == 0 ||
645 			 strcmp(sptmp->mountpoint, "/dummy") == 0)
646 			sptmp->letter = 'a';
647 		else if (strcasecmp(sptmp->mountpoint, "swap") == 0)
648 			sptmp->letter = 'b';
649 		else
650 			sptmp->letter = letter++;
651 	}
652 
653 	return(sp);
654 }
655 
656 /*
657  * Find the subpartition description in the given storage
658  * description whose mountpoint matches the given string exactly.
659  */
660 struct subpartition *
661 subpartition_find(const struct slice *s, const char *fmt, ...)
662 {
663 	struct subpartition *sp = s->subpartition_head;
664 	char *mountpoint;
665 	va_list args;
666 
667 	va_start(args, fmt);
668 	vasprintf(&mountpoint, fmt, args);
669 	va_end(args);
670 
671 	while (sp != NULL) {
672 		if (strcmp(mountpoint, sp->mountpoint) == 0) {
673 			free(mountpoint);
674 			return(sp);
675 		}
676 		sp = sp->next;
677 	}
678 
679 	free(mountpoint);
680 	return(NULL);
681 }
682 
683 /*
684  * Find the subpartition description in the given storage
685  * description where the given filename would presumably
686  * reside.  This is the subpartition whose mountpoint is
687  * the longest match for the given filename.
688  */
689 struct subpartition *
690 subpartition_of(const struct slice *s, const char *fmt, ...)
691 {
692 	struct subpartition *sp = s->subpartition_head;
693 	struct subpartition *csp = NULL;
694 	size_t len = 0;
695 	char *filename;
696 	va_list args;
697 
698 	va_start(args, fmt);
699 	vasprintf(&filename, fmt, args);
700 	va_end(args);
701 
702 	while (sp != NULL) {
703 		if (strlen(sp->mountpoint) > len &&
704 		    strlen(sp->mountpoint) <= strlen(filename) &&
705 		    strncmp(filename, sp->mountpoint, strlen(sp->mountpoint)) == 0) {
706 				csp = sp;
707 				len = strlen(csp->mountpoint);
708 		}
709 		sp = sp->next;
710 	}
711 
712 	free(filename);
713 	return(csp);
714 }
715 
716 struct subpartition *
717 subpartition_find_capacity(const struct slice *s, long capacity)
718 {
719 	struct subpartition *sp = s->subpartition_head;
720 
721 	while (sp != NULL) {
722 		if (sp->capacity == capacity)
723 			return(sp);
724 		sp = sp->next;
725 	}
726 
727 	return(NULL);
728 }
729 
730 struct subpartition *
731 subpartition_next(const struct subpartition *sp)
732 {
733 	return(sp->next);
734 }
735 
736 int
737 subpartition_get_pfs(const struct subpartition *sp)
738 {
739 	return(sp->pfs);
740 }
741 
742 /*
743  * Returns the name of the device node used to represent
744  * the subpartition, either by serial number or traditional style.
745  * Note that the storage used for the returned string is static,
746  * and the string is overwritten each time this function is called.
747  */
748 const char *
749 subpartition_get_device_name(const struct subpartition *sp)
750 {
751 	static char tmp_dev_name[256];
752 
753 	if (sp->parent->parent->serno != NULL)
754 		snprintf(tmp_dev_name, 256, "serno/%s.s%d%c",
755 		    sp->parent->parent->serno, sp->parent->number, sp->letter);
756 	else
757 		snprintf(tmp_dev_name, 256, "%ss%d%c",
758 		    sp->parent->parent->device, sp->parent->number, sp->letter);
759 	return(tmp_dev_name);
760 }
761 
762 const char *
763 subpartition_get_mountpoint(const struct subpartition *sp)
764 {
765 	return(sp->mountpoint);
766 }
767 
768 char
769 subpartition_get_letter(const struct subpartition *sp)
770 {
771 	return(sp->letter);
772 }
773 
774 unsigned long
775 subpartition_get_fsize(const struct subpartition *sp)
776 {
777 	return(sp->fsize);
778 }
779 
780 unsigned long
781 subpartition_get_bsize(const struct subpartition *sp)
782 {
783 	return(sp->bsize);
784 }
785 
786 unsigned long
787 subpartition_get_capacity(const struct subpartition *sp)
788 {
789 	return(sp->capacity);
790 }
791 
792 int
793 subpartition_is_swap(const struct subpartition *sp)
794 {
795 	return(sp->is_swap);
796 }
797 
798 int
799 subpartition_is_softupdated(const struct subpartition *sp)
800 {
801 	return(sp->softupdates);
802 }
803 int
804 subpartition_is_mfsbacked(const struct subpartition *sp)
805 {
806 	return(sp->mfsbacked);
807 }
808 
809 int
810 subpartition_count(const struct slice *s)
811 {
812 	struct subpartition *sp = s->subpartition_head;
813 	int count = 0;
814 
815 	while (sp != NULL) {
816 		count++;
817 		sp = sp->next;
818 	}
819 
820 	return(count);
821 }
822 
823 void
824 subpartitions_free(struct slice *s)
825 {
826 	struct subpartition *sp = s->subpartition_head, *next;
827 
828 	while (sp != NULL) {
829 		next = sp->next;
830 		free(sp->mountpoint);
831 		AURA_FREE(sp, subpartition);
832 		sp = next;
833 	}
834 
835 	s->subpartition_head = NULL;
836 	s->subpartition_tail = NULL;
837 }
838 
839 long
840 measure_activated_swap(const struct i_fn_args *a)
841 {
842 	FILE *p;
843 	char line[256];
844 	char *word;
845 	long swap = 0;
846 
847 	if ((p = aura_popen("%s%s -k", "r", a->os_root, cmd_name(a, "SWAPINFO"))) == NULL)
848 		return(0);
849 	while (fgets(line, 255, p) != NULL) {
850 		if ((word = strtok(line, " \t")) == NULL)
851 			continue;
852 		if (strcmp(word, "Device") == 0)
853 			continue;
854 		if ((word = strtok(NULL, " \t")) == NULL)
855 			continue;
856 		swap += atol(word);
857 	}
858 	aura_pclose(p);
859 
860 	return(swap / 1024);
861 }
862 
863 long
864 measure_activated_swap_from_slice(const struct i_fn_args *a,
865     const struct disk *d, const struct slice *s)
866 {
867 	FILE *p;
868 	char *dev, *word;
869 	char line[256];
870 	long swap = 0;
871 
872 	if ((p = aura_popen("%s%s -k", "r", a->os_root, cmd_name(a, "SWAPINFO"))) == NULL)
873 		return(0);
874 
875 	asprintf(&dev, "/dev/%ss%d", d->device, s->number);
876 
877 	while (fgets(line, 255, p) != NULL) {
878 		if ((word = strtok(line, " \t")) == NULL)
879 			continue;
880 		if (strcmp(word, "Device") == 0)
881 			continue;
882 		if (strstr(word, dev) != word)
883 			continue;
884 		if ((word = strtok(NULL, " \t")) == NULL)
885 			continue;
886 		swap += atol(word);
887 	}
888 	aura_pclose(p);
889 	free(dev);
890 
891 	return(swap / 1024);
892 }
893 
894 long
895 measure_activated_swap_from_disk(const struct i_fn_args *a,
896 				 const struct disk *d)
897 {
898 	struct slice *s;
899 	long swap = 0;
900 
901 	for (s = d->slice_head; s != NULL; s = s->next)
902 		swap += measure_activated_swap_from_slice(a, d, s);
903 
904 	return(swap);
905 }
906