]> git.saurik.com Git - apple/system_cmds.git/blame - gcore.tproj/vanilla.c
system_cmds-735.tar.gz
[apple/system_cmds.git] / gcore.tproj / vanilla.c
CommitLineData
cf37c299
A
1/*
2 * Copyright (c) 2016 Apple Inc. All rights reserved.
3 */
4
5#include "options.h"
6#include "vm.h"
7#include "region.h"
8#include "utils.h"
9#include "dyld.h"
10#include "threads.h"
11#include "vanilla.h"
12#include "sparse.h"
13
14#include <sys/types.h>
15#include <sys/sysctl.h>
16#include <sys/stat.h>
17#include <sys/mman.h>
18#include <libproc.h>
19
20#include <stdio.h>
21#include <string.h>
22#include <strings.h>
23#include <stdlib.h>
24#include <stdarg.h>
25#include <signal.h>
26#include <unistd.h>
27#include <errno.h>
28#include <ctype.h>
29#include <fcntl.h>
30#include <assert.h>
31#include <sysexits.h>
32
33#include <mach/mach.h>
34
35walk_return_t
36vanilla_region_optimization(struct region *r, __unused void *arg)
37{
38 assert(0 != R_SIZE(r));
39
40 /*
41 * Elide unreadable regions
42 */
43 if ((r->r_info.max_protection & VM_PROT_READ) != VM_PROT_READ) {
44 if (opt->debug)
45 printr(r, "eliding unreadable region\n");
46 return WALK_DELETE_REGION;
47 }
48#ifdef CONFIG_SUBMAP
49 /*
50 * Elide submaps (here for debugging purposes?)
51 */
52 if (r->r_info.is_submap) {
53 if (opt->debug)
54 printr(r, "eliding submap\n");
55 return WALK_DELETE_REGION;
56 }
57#endif
58 /*
59 * Elide guard regions
60 */
61 if (r->r_info.protection == VM_PROT_NONE &&
62 (VM_MEMORY_STACK == r->r_info.user_tag ||
63 VM_MEMORY_MALLOC == r->r_info.user_tag)) {
64 if (opt->debug) {
65 hsize_str_t hstr;
66 printr(r, "eliding %s - guard\n",
67 str_hsize(hstr, R_SIZE(r)));
68 }
69 return WALK_DELETE_REGION;
70 }
71 return WALK_CONTINUE;
72}
73
74/*
75 * (Paranoid validation + debugging assistance.)
76 */
77static void
78validate_core_header(const native_mach_header_t *mh, off_t corefilesize)
79{
80 if (opt->debug)
81 printf("Core file: mh %p ncmds %u sizeofcmds %u\n",
82 mh, mh->ncmds, mh->sizeofcmds);
83
84 const struct load_command *lc = (const void *)(mh + 1);
85 for (unsigned i = 0; i < mh->ncmds; i++) {
86
87 if ((uintptr_t)lc < (uintptr_t)mh ||
88 (uintptr_t)lc > (uintptr_t)mh + mh->sizeofcmds) {
89 warnx("load command %p outside mach header range [%p, 0x%lx]?",
90 lc, mh, (uintptr_t)mh + mh->sizeofcmds);
91 abort();
92 }
93 if (opt->debug)
94 printf("lc %p cmd %u cmdsize %u ", lc, lc->cmd, lc->cmdsize);
95
96 const native_segment_command_t *sc;
97 const struct proto_coreinfo_command *cic;
98 const struct proto_fileref_command *frc;
99 const struct thread_command *tc;
100
101 switch (lc->cmd) {
102 case NATIVE_LC_SEGMENT:
103 sc = (const void *)lc;
104 if (opt->debug) {
105 printf("%8s: mem %llx-%llx file %lld-%lld %x/%x flags %x\n",
106 "SEGMENT",
107 (mach_vm_offset_t)sc->vmaddr,
108 (mach_vm_offset_t)sc->vmaddr + sc->vmsize,
109 (off_t)sc->fileoff,
110 (off_t)sc->fileoff + (off_t)sc->filesize,
111 sc->initprot, sc->maxprot, sc->flags);
112 }
113 if ((off_t)sc->fileoff < mh->sizeofcmds ||
114 (off_t)sc->filesize < 0) {
115 warnx("bad segment command");
116 abort();
117 }
118 if ((off_t)sc->fileoff > corefilesize ||
119 (off_t)sc->fileoff + (off_t)sc->filesize > corefilesize) {
120 /*
121 * We may have run out of space to write the data
122 */
123 warnx("segment command points beyond end of file");
124 }
125 break;
126
127 case proto_LC_COREINFO:
128 cic = (const void *)lc;
129 if (opt->debug) {
130 uuid_string_t uustr;
131 uuid_unparse_lower(cic->uuid, uustr);
132 printf("%8s: version %d type %d uuid %s addr %llx dyninfo %llx\n",
133 "COREINFO", cic->version, cic->type, uustr, cic->address, cic->dyninfo);
134 }
135 if (cic->version < 1 ||
136 cic->type != proto_CORETYPE_USER) {
137 warnx("bad coreinfo command");
138 abort();
139 }
140 break;
141
142 case proto_LC_FILEREF:
143 frc = (const void *)lc;
144 const char *nm = frc->filename.offset + (char *)lc;
145 if (opt->debug) {
146 uuid_string_t uustr;
147 uuid_unparse_lower(frc->uuid, uustr);
148 printf("%8s: mem %llx-%llx file %lld-%lld %x/%x '%s' %.12s..\n",
149 "FILEREF",
150 frc->vmaddr,
151 frc->vmaddr + frc->vmsize,
152 (off_t)frc->fileoff,
153 (off_t)frc->fileoff + (off_t)frc->filesize,
154 frc->initprot, frc->maxprot, nm, uustr);
155 }
156 if (nm <= (caddr_t)lc ||
157 nm > (caddr_t)lc + lc->cmdsize ||
158 (off_t)frc->fileoff < 0 || (off_t)frc->filesize < 0) {
159 warnx("bad fileref command");
160 abort();
161 }
162 break;
163
164 case LC_THREAD:
165 tc = (const void *)lc;
166 if (opt->debug)
167 printf("%8s:\n", "THREAD");
168 uint32_t *wbuf = (void *)(tc + 1);
169 do {
170 const uint32_t flavor = *wbuf++;
171 const uint32_t count = *wbuf++;
172
173 if (opt->debug) {
174 printf(" flavor %u count %u\n", flavor, count);
175 if (count) {
176 boolean_t nl = false;
177 for (unsigned k = 0; k < count; k++) {
178 if (0 == (k & 7))
179 printf(" [%3u] ", k);
180 printf("%08x ", *wbuf++);
181 if (7 == (k & 7)) {
182 printf("\n");
183 nl = true;
184 } else
185 nl = false;
186 }
187 if (!nl)
188 printf("\n");
189 }
190 } else
191 wbuf += count;
192
193 if (!VALID_THREAD_STATE_FLAVOR(flavor)) {
194 warnx("bad thread state flavor");
195 abort();
196 }
197 } while ((caddr_t) wbuf < (caddr_t)tc + tc->cmdsize);
198 break;
199
200 default:
201 warnx("unknown cmd %u in header\n", lc->cmd);
202 abort();
203 }
204 if (lc->cmdsize)
205 lc = (const void *)((caddr_t)lc + lc->cmdsize);
206 else
207 break;
208 }
209}
210
211/*
212 * The vanilla Mach-O core file consists of:
213 *
214 * - A Mach-O header of type MH_CORE
215 *
216 * A set of load commands of the following types:
217 *
218 * - LC_SEGMENT{,_64} pointing at memory content in the file,
219 * each chunk consisting of a contiguous region. Regions may be zfod
220 * (no file content present) or content may be compressed (experimental)
221 *
222 * - prototype_LC_COREINFO (experimental), pointing at dyld (10.12 onwards)
223 *
224 * - prototype_LC_FILEREF (experimental) pointing at memory
225 * content to be mapped in from another file at various offsets
226 *
227 * - LC_THREAD commands with state for each thread
228 *
229 * These load commands are followed by the relevant contents of memory,
230 * pointed to by the various commands.
231 */
232
233int
234coredump_write(
235 const task_t task,
236 const int fd,
237 struct regionhead *rhead,
238 const uuid_t aout_uuid,
239 mach_vm_offset_t aout_load_addr,
240 mach_vm_offset_t dyld_aii_addr)
241{
242 struct size_segment_data ssda;
243 bzero(&ssda, sizeof (ssda));
244
245 if (walk_region_list(rhead, region_size_memory, &ssda) < 0) {
246 warnx(0, "cannot count segments");
247 return EX_OSERR;
248 }
249
250 unsigned thread_count = 0;
251 mach_port_t *threads = NULL;
252 kern_return_t ret = task_threads(task, &threads, &thread_count);
253 if (KERN_SUCCESS != ret || thread_count < 1) {
254 err_mach(ret, NULL, "cannot retrieve threads");
255 thread_count = 0;
256 }
257
258 if (opt->debug) {
259 print_memory_region_header();
260 walk_region_list(rhead, region_print_memory, NULL);
261 }
262
263 size_t headersize = sizeof (native_mach_header_t) +
264 thread_count * sizeof_LC_THREAD() +
265 ssda.ssd_fileref.headersize +
266 ssda.ssd_zfod.headersize +
267 ssda.ssd_vanilla.headersize +
268 ssda.ssd_sparse.headersize;
269 if (opt->coreinfo)
270 headersize += sizeof (struct proto_coreinfo_command);
271
272 void *header = calloc(1, headersize);
273 if (NULL == header)
274 errx(EX_OSERR, "out of memory for header");
275
276 native_mach_header_t *mh = make_corefile_mach_header(header);
277 struct load_command *lc = (void *)(mh + 1);
278
279 if (opt->coreinfo) {
280 const struct proto_coreinfo_command *cc =
281 make_coreinfo_command(mh, lc, aout_uuid, aout_load_addr, dyld_aii_addr);
282 lc = (void *)((caddr_t)cc + cc->cmdsize);
283 }
284
285 if (opt->debug) {
286 const unsigned long fileref_count = ssda.ssd_fileref.count;
287 const unsigned long segment_count = fileref_count +
288 ssda.ssd_zfod.count + ssda.ssd_vanilla.count + ssda.ssd_sparse.count;
289 printf("Dumping %lu memory segments", segment_count);
290 if (0 != fileref_count)
291 printf(" (including %lu file reference%s (%lu bytes))",
292 fileref_count, 1 == fileref_count ? "" : "s",
293 ssda.ssd_fileref.headersize);
294 printf("\n");
295 }
296
297 vm_size_t pagesize = ((vm_offset_t)1 << pageshift_host);
298 vm_offset_t pagemask = (vm_offset_t)(pagesize - 1);
299
300 struct write_segment_data wsda = {
301 .wsd_task = task,
302 .wsd_mh = mh,
303 .wsd_lc = lc,
304 .wsd_fd = fd,
305 .wsd_foffset = ((vm_offset_t)headersize + pagemask) & ~pagemask,
306 .wsd_nwritten = 0,
307 };
308
309 int ecode = 0;
310 if (0 != walk_region_list(rhead, region_write_memory, &wsda))
311 ecode = EX_IOERR;
312
313 del_region_list(rhead);
314
315 struct thread_command *tc = (void *)wsda.wsd_lc;
316
317 for (unsigned t = 0; t < thread_count; t++) {
318 dump_thread_state(mh, tc, threads[t]);
319 mach_port_deallocate(mach_task_self(), threads[t]);
320 tc = (void *)((caddr_t)tc + tc->cmdsize);
321 }
322
323 /*
324 * Even if we've run out of space, try our best to
325 * write out the header.
326 */
327 if (-1 == pwrite(fd, header, headersize, 0))
328 ecode = EX_IOERR;
329 else
330 wsda.wsd_nwritten += headersize;
331
332 validate_core_header(mh, wsda.wsd_foffset);
333
334 if (ecode)
335 warnx("failed to write core file correctly");
336 else if (opt->verbose) {
337 hsize_str_t hsz;
338 printf("Wrote %s to corefile ", str_hsize(hsz, wsda.wsd_nwritten));
339 printf("(memory image %s", str_hsize(hsz, ssda.ssd_vanilla.memsize));
340 if (ssda.ssd_sparse.memsize)
341 printf("+%s", str_hsize(hsz, ssda.ssd_sparse.memsize));
342 if (ssda.ssd_fileref.memsize)
343 printf(", referenced %s", str_hsize(hsz, ssda.ssd_fileref.memsize));
344 if (ssda.ssd_zfod.memsize)
345 printf(", zfod %s", str_hsize(hsz, ssda.ssd_zfod.memsize));
346 printf(")\n");
347 }
348 free(header);
349 return ecode;
350}
351
352int
353coredump(task_t task, int fd)
354{
355 /* this is the shared cache id, if any */
356 uuid_t sc_uuid;
357 uuid_clear(sc_uuid);
358
359 dyld_process_info dpi = get_task_dyld_info(task);
360 if (dpi) {
361 get_sc_uuid(dpi, sc_uuid);
362 }
363
364 /* this group is for LC_COREINFO */
365 mach_vm_offset_t dyld_addr = 0; // all_image_infos -or- dyld mach header
366 mach_vm_offset_t aout_load_addr = 0;
367 uuid_t aout_uuid;
368 uuid_clear(aout_uuid);
369
370 /*
371 * Walk the address space
372 */
373 int ecode = 0;
374 struct regionhead *rhead = coredump_prepare(task, sc_uuid);
375 if (NULL == rhead) {
376 ecode = EX_OSERR;
377 goto done;
378 }
379
380 if (opt->debug)
381 printf("Optimizing dump content\n");
382 walk_region_list(rhead, vanilla_region_optimization, NULL);
383
384 if (dpi) {
385 if (opt->coreinfo || opt->sparse) {
386 /*
387 * Snapshot dyld's info ..
388 */
389 if (!libent_build_nametable(task, dpi))
390 warnx("error parsing dyld data => ignored");
391 else {
392 if (opt->coreinfo) {
393 /*
394 * Find the a.out load address and uuid, and the dyld mach header for the coreinfo
395 */
396 const struct libent *le;
397 if (NULL != (le = libent_lookup_first_bytype(MH_EXECUTE))) {
398 aout_load_addr = le->le_mhaddr;
399 uuid_copy(aout_uuid, le->le_uuid);
400 }
401 if (NULL != (le = libent_lookup_first_bytype(MH_DYLINKER))) {
402 dyld_addr = le->le_mhaddr;
403 }
404 }
405 if (opt->sparse) {
406 /*
407 * Use dyld's view of what's being used in the address
408 * space to shrink the dump.
409 */
410 if (0 == walk_region_list(rhead, decorate_memory_region, (void *)dpi)) {
411 if (opt->debug)
412 printf("Performing sparse dump optimization(s)\n");
413 walk_region_list(rhead, sparse_region_optimization, NULL);
414 } else {
415 walk_region_list(rhead, undecorate_memory_region, NULL);
416 warnx("error parsing dyld data => ignored");
417 }
418 }
419 }
420 }
421 free_task_dyld_info(dpi);
422 }
423
424 if (opt->debug)
425 printf("Optimization(s) done\n");
426done:
427 if (0 == ecode)
428 ecode = coredump_write(task, fd, rhead, aout_uuid, aout_load_addr, dyld_addr);
429 return ecode;
430}
431
432#ifdef CONFIG_REFSC
433
434struct find_shared_cache_args {
435 task_t fsc_task;
436 vm_object_id_t fsc_object_id;
437 vm32_object_id_t fsc_region_object_id;
438 uuid_t fsc_uuid;
439 const struct libent *fsc_le;
440 int fsc_fd;
441};
442
443/*
444 * This is "find the objid of the first shared cache" in the shared region.
445 */
446static walk_return_t
447find_shared_cache(struct region *r, void *arg)
448{
449 struct find_shared_cache_args *fsc = arg;
450
451 if (!r->r_insharedregion)
452 return WALK_CONTINUE; /* wrong address range! */
453 if (0 != r->r_info.user_tag)
454 return WALK_CONTINUE; /* must be tag zero */
455 if ((VM_PROT_READ | VM_PROT_EXECUTE) != r->r_info.protection ||
456 r->r_info.protection != r->r_info.max_protection)
457 return WALK_CONTINUE; /* must be r-x / r-x */
458 if (r->r_pageinfo.offset != 0)
459 return WALK_CONTINUE; /* must map beginning of file */
460
461 if (opt->debug) {
462 hsize_str_t hstr;
463 printf("Examining shared cache candidate %llx-%llx (%s)\n",
464 R_ADDR(r), R_ENDADDR(r), str_hsize(hstr, R_SIZE(r)));
465 }
466
467 struct copied_dyld_cache_header *ch;
468 mach_msg_type_number_t chlen = sizeof (*ch);
469 kern_return_t ret = mach_vm_read(fsc->fsc_task, R_ADDR(r), sizeof (*ch), (vm_offset_t *)&ch, &chlen);
470
471 if (KERN_SUCCESS != ret) {
472 err_mach(ret, NULL, "mapping candidate shared region");
473 return WALK_CONTINUE;
474 }
475
476 uuid_t scuuid;
477 if (get_uuid_from_shared_cache_mapping(ch, chlen, scuuid) &&
478 uuid_compare(scuuid, fsc->fsc_uuid) == 0) {
479 if (opt->debug > 2) {
480 uuid_string_t uustr;
481 uuid_unparse_lower(fsc->fsc_uuid, uustr);
482 printr(r, "found shared cache %s here\n", uustr);
483 }
484 if (!r->r_info.external_pager) {
485 if (opt->debug)
486 printf("Hmm. Found shared cache magic# + uuid, but not externally paged?\n");
487#if 0
488 return WALK_CONTINUE; /* should be "paged" from a file */
489#endif
490 }
491 // This is the ID associated with the first page of the mapping
492 fsc->fsc_object_id = r->r_pageinfo.object_id;
493 // This is the ID associated with the region
494 fsc->fsc_region_object_id = r->r_info.object_id;
495 }
496 mach_vm_deallocate(mach_task_self(), (vm_offset_t)ch, chlen);
497 if (fsc->fsc_object_id) {
498 if (opt->debug) {
499 uuid_string_t uu;
500 uuid_unparse_lower(fsc->fsc_uuid, uu);
501 printf("Shared cache objid %llx uuid %s\n",
502 fsc->fsc_object_id, uu);
503 }
504 return WALK_TERMINATE;
505 }
506 return WALK_CONTINUE;
507}
508
509static boolean_t
510compare_region_with_shared_cache(const struct region *r, struct find_shared_cache_args *fsc)
511{
512 struct stat st;
513 if (-1 == fstat(fsc->fsc_fd, &st)) {
514 if (opt->debug)
515 printr(r, "%s - %s\n",
516 fsc->fsc_le->le_filename, strerror(errno));
517 return false;
518 }
519 void *file = mmap(NULL, (size_t)R_SIZE(r), PROT_READ, MAP_PRIVATE, fsc->fsc_fd, r->r_pageinfo.offset);
520 if ((void *)-1L == file) {
521 if (opt->debug)
522 printr(r, "mmap %s - %s\n", fsc->fsc_le->le_filename, strerror(errno));
523 return false;
524 }
525 madvise(file, (size_t)R_SIZE(r), MADV_SEQUENTIAL);
526
527 vm_offset_t data = 0;
528 mach_msg_type_number_t data_count;
529 const kern_return_t kr = mach_vm_read(fsc->fsc_task, R_ADDR(r), R_SIZE(r), &data, &data_count);
530
531 if (KERN_SUCCESS != kr || data_count < R_SIZE(r)) {
532 err_mach(kr, r, "mach_vm_read()");
533 munmap(file, (size_t)R_SIZE(r));
534 return false;
535 }
536
537 mach_vm_size_t cmpsize = data_count;
538
539#ifdef RDAR_23744374
540 /*
541 * Now we have the corresponding regions mapped, we should be
542 * able to compare them. There's just one last twist that relates
543 * to heterogenous pagesize systems: rdar://23744374
544 */
545 if (st.st_size < (off_t)(r->r_pageinfo.offset + cmpsize) &&
546 pageshift_host < pageshift_app) {
547 /*
548 * Looks like we're about to map close to the end of the object.
549 * Check what's really mapped there and reduce the size accordingly.
550 */
551 if (!is_actual_size(fsc->fsc_task, r, &cmpsize)) {
552 if (opt->debug)
553 printr(r, "narrowing the comparison (%llu "
554 "-> %llu)\n", R_SIZE(r), cmpsize);
555 }
556 }
557#endif
558
559 mach_vm_behavior_set(mach_task_self(), data, data_count, VM_BEHAVIOR_SEQUENTIAL);
560
561 const boolean_t thesame = memcmp(file, (void *)data, (size_t)cmpsize) == 0;
562#if 0
563 if (!thesame) {
564 int diffcount = 0;
565 int samecount = 0;
566 const char *f = file;
567 const char *d = (void *)data;
568 for (mach_vm_size_t off = 0; off < cmpsize; off += 4096) {
569 if (memcmp(f, d, 4096) != 0) {
570 diffcount++;
571 } else samecount++;
572 f += 4096;
573 d += 4096;
574 }
575 if (diffcount)
576 printr(r, "%d of %d pages different\n", diffcount, diffcount + samecount);
577 }
578#endif
579 mach_vm_deallocate(mach_task_self(), data, data_count);
580 munmap(file, (size_t)R_SIZE(r));
581
582 if (!thesame && opt->debug)
583 printr(r, "mapped file (%s) region is modified\n", fsc->fsc_le->le_filename);
584 return thesame;
585}
586
587static walk_return_t
588label_shared_cache(struct region *r, void *arg)
589{
590 struct find_shared_cache_args *fsc = arg;
591
592 if (!r->r_insharedregion)
593 return WALK_CONTINUE;
594 if (!r->r_info.external_pager)
595 return WALK_CONTINUE;
596 if (r->r_pageinfo.object_id != fsc->fsc_object_id) {
597 /* wrong object, or first page already modified */
598 return WALK_CONTINUE;
599 }
600 if (((r->r_info.protection | r->r_info.max_protection) & VM_PROT_WRITE) != 0) {
601 /* writable, but was it written? */
602 if (r->r_info.pages_dirtied + r->r_info.pages_swapped_out != 0)
603 return WALK_CONTINUE; // a heuristic ..
604 if (!compare_region_with_shared_cache(r, fsc)) {
605 /* bits don't match */
606 return WALK_CONTINUE;
607 }
608 }
609
610 if (opt->debug > 2) {
611 /* this validation is -really- expensive */
612 if (!compare_region_with_shared_cache(r, fsc))
613 printr(r, "WARNING: region should match, but doesn't\n");
614 }
615
616 /*
617 * This mapped file segment will be represented as a reference
618 * to the file, rather than as a copy of the file.
619 */
620 const struct libent *le = libent_lookup_byuuid(fsc->fsc_uuid);
621 r->r_fileref = calloc(1, sizeof (*r->r_fileref));
622 if (r->r_fileref) {
623 r->r_fileref->fr_libent = le;
624 if (r->r_fileref->fr_libent) {
625 r->r_fileref->fr_offset = r->r_pageinfo.offset;
626 r->r_op = &fileref_ops;
627 } else {
628 free(r->r_fileref);
629 r->r_fileref = NULL;
630 }
631 }
632 return WALK_CONTINUE;
633}
634#endif /* CONFIG_REFSC */
635
636struct regionhead *
637coredump_prepare(task_t task, uuid_t sc_uuid)
638{
639 struct regionhead *rhead = build_region_list(task);
640
641 if (opt->debug) {
642 print_memory_region_header();
643 walk_region_list(rhead, region_print_memory, NULL);
644 }
645
646 if (uuid_is_null(sc_uuid))
647 return rhead;
648
649 /*
650 * Name the shared cache, if we can
651 */
652 char *nm = shared_cache_filename(sc_uuid);
653 const struct libent *le;
654
655 if (NULL != nm)
656 le = libent_insert(nm, sc_uuid, 0, NULL);
657 else {
658 le = libent_insert("(shared cache)", sc_uuid, 0, NULL);
659 if (opt->verbose){
660 uuid_string_t uustr;
661 uuid_unparse_lower(sc_uuid, uustr);
662 printf("Shared cache UUID: %s, but no filename => ignored\n", uustr);
663 return rhead;
664 }
665 }
666
667#ifdef CONFIG_REFSC
668 if (opt->scfileref) {
669 /*
670 * See if we can replace entire regions with references to the shared cache
671 * by looking at the VM meta-data about those regions.
672 */
673 if (opt->debug) {
674 uuid_string_t uustr;
675 uuid_unparse_lower(sc_uuid, uustr);
676 printf("Searching for shared cache with uuid %s\n", uustr);
677 }
678
679 /*
680 * Identify the regions mapping the shared cache by comparing the UUID via
681 * dyld with the UUID of likely-looking mappings in the right address range
682 */
683 struct find_shared_cache_args fsca;
684 bzero(&fsca, sizeof (fsca));
685 fsca.fsc_task = task;
686 uuid_copy(fsca.fsc_uuid, sc_uuid);
687 fsca.fsc_fd = -1;
688
689 walk_region_list(rhead, find_shared_cache, &fsca);
690
691 if (0 == fsca.fsc_object_id) {
692 printf("Cannot identify the shared cache region(s) => ignored\n");
693 } else {
694 if (opt->verbose)
695 printf("Referenced %s\n", nm);
696 fsca.fsc_le = le;
697 fsca.fsc_fd = open(fsca.fsc_le->le_filename, O_RDONLY);
698
699 walk_region_list(rhead, label_shared_cache, &fsca);
700
701 close(fsca.fsc_fd);
702 free(nm);
703 }
704 }
705#endif /* CONFIG_REFSC */
706
707 return rhead;
708}