1/*
2 * Copyright (c) 2006-2007 Apple Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23/*
24 *  BLCreateBooterInformationDictionary.c
25 *  bless
26 *
27 *  Created by Shantonu Sen on 5/22/06.
28 *  Copyright 2006-2007 Apple Inc. All Rights Reserved.
29 *
30 */
31
32#include <stdlib.h>
33#include <unistd.h>
34#include <sys/stat.h>
35#include <sys/param.h>
36#include <sys/mount.h>
37
38#include <mach/mach_error.h>
39
40#include <IOKit/IOKitLib.h>
41#include <IOKit/IOBSD.h>
42
43#include <CoreFoundation/CoreFoundation.h>
44
45#include "bless.h"
46#include "bless_private.h"
47
48
49#include <IOKit/storage/IOMedia.h>
50#include <IOKit/storage/IOPartitionScheme.h>
51#if SUPPORT_APPLE_PARTITION_MAP
52#include <IOKit/storage/IOApplePartitionScheme.h>
53#endif
54#include <IOKit/storage/IOGUIDPartitionScheme.h>
55#include <IOKit/storage/IOStorageProtocolCharacteristics.h>
56
57static int addRAIDInfo(BLContextPtr context, CFDictionaryRef dict,
58            CFMutableArrayRef dataPartitions,
59            CFMutableArrayRef booterPartitions,
60            CFMutableArrayRef systemPartitions);
61
62
63static int addDataPartitionInfo(BLContextPtr context, io_service_t dataPartition,
64                       CFMutableArrayRef dataPartitions,
65                       CFMutableArrayRef booterPartitions,
66                       CFMutableArrayRef systemPartitions);
67
68static int addPreferredSystemPartitionInfo(BLContextPtr context,
69                                CFMutableArrayRef systemPartitions,
70                                           bool foundPreferred);
71
72
73bool isPreferredSystemPartition(BLContextPtr context, CFStringRef bsdName);
74
75/*
76 * For the given device, we return the set of Auxiliary Partitions and
77 * System Partitions. System Partitions are not OS-specific. This routine
78 * works for both APM and GPT disks, as well as IOMedia filters/aggregates
79 * Using those partition map types.
80 *
81 * A given partition may not have an Auxiliary Partitions nor System Partitions,
82 * for example a single HFS+ partition on an APM disk, or it may have many
83 * APs and SPs, for example a 4-member RAID mirror on GPT disks.
84 */
85
86int BLCreateBooterInformationDictionary(BLContextPtr context, const char * bsdName,
87                                        CFDictionaryRef *outDict)
88{
89    CFMutableArrayRef dataPartitions = NULL;
90    CFMutableArrayRef booterPartitions = NULL;
91    CFMutableArrayRef systemPartitions = NULL;
92    CFMutableDictionaryRef booters = NULL;
93
94    CFArrayRef      array;
95
96    CFTypeRef               bootData = NULL;
97
98    io_service_t            rootDev;
99    int                     ret = 0;
100    bool                    gotOne;
101
102    dataPartitions = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
103    if(dataPartitions == NULL)
104        return 1;
105
106    booterPartitions = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
107    if(booterPartitions == NULL)
108        return 1;
109
110    systemPartitions = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
111    if(systemPartitions == NULL)
112        return 1;
113
114    booters = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
115                                        &kCFTypeDictionaryKeyCallBacks,
116                                        &kCFTypeDictionaryValueCallBacks);
117    if(booters == NULL)
118        return 1;
119
120
121    rootDev = IOServiceGetMatchingService(kIOMasterPortDefault,
122                                          IOBSDNameMatching(kIOMasterPortDefault, 0, bsdName));
123
124    if(rootDev == IO_OBJECT_NULL) {
125        CFRelease(booters);
126
127        contextprintf(context, kBLLogLevelError,
128                      "Could not get IOService for %s\n", bsdName);
129        return 2;
130    }
131
132    if(!IOObjectConformsTo(rootDev,kIOMediaClass)) {
133        CFRelease(booters);
134		CFRelease(dataPartitions);
135		CFRelease(booterPartitions);
136		CFRelease(systemPartitions);
137
138        IOObjectRelease(rootDev);
139
140        contextprintf(context, kBLLogLevelError,
141                      "%s is not an IOMedia object\n", bsdName);
142        return 2;
143    }
144
145    bootData = IORegistryEntrySearchCFProperty( rootDev,
146                                            kIOServicePlane,
147                                            CFSTR(kIOBootDeviceKey),
148                                            kCFAllocatorDefault,
149                                            kIORegistryIterateRecursively|
150                                            kIORegistryIterateParents);
151
152	if(bootData) {
153
154        // if there's boot data, this is an IOMedia filter/aggregate publishing
155        // its data members
156        if(CFGetTypeID(bootData) == CFArrayGetTypeID()) {
157            CFIndex i, count = CFArrayGetCount(bootData);
158
159            for(i=0; i < count; i++) {
160                CFDictionaryRef dict = CFArrayGetValueAtIndex((CFArrayRef)bootData,i);
161
162                ret = addRAIDInfo(context, dict,
163                                  dataPartitions,
164                                  booterPartitions,
165                                  systemPartitions);
166                if(ret) {
167                    break;
168                }
169
170            }
171        } else if( CFGetTypeID(bootData) == CFDictionaryGetTypeID()) {
172
173            ret = addRAIDInfo(context, (CFDictionaryRef)bootData,
174                              dataPartitions,
175                              booterPartitions,
176                              systemPartitions);
177        } else {
178            contextprintf(context, kBLLogLevelError,
179                          "Invalid boot data for %s\n", bsdName);
180
181            ret = 5;;
182        }
183
184        if(ret) {
185			CFRelease(bootData);
186            CFRelease(booters);
187			CFRelease(dataPartitions);
188			CFRelease(booterPartitions);
189			CFRelease(systemPartitions);
190
191            IOObjectRelease(rootDev);
192
193            return ret;
194
195        }
196
197		CFRelease(bootData);
198
199	} else {
200        ret = addDataPartitionInfo(context, rootDev,
201                                   dataPartitions,
202                                   booterPartitions,
203                                   systemPartitions);
204        if(ret) {
205            CFRelease(booters);
206			CFRelease(dataPartitions);
207			CFRelease(booterPartitions);
208			CFRelease(systemPartitions);
209
210            IOObjectRelease(rootDev);
211
212            return ret;
213        }
214    }
215
216
217    IOObjectRelease(rootDev);
218
219	goto gotinfo;
220gotinfo:
221
222    if(getenv("BL_PRIMARY_BOOTER_INDEX")) {
223        char *bindex = getenv("BL_PRIMARY_BOOTER_INDEX");
224        CFIndex index = atoi(bindex);
225
226        if(index >= 0 && index < CFArrayGetCount(booterPartitions)) {
227            CFArrayExchangeValuesAtIndices(booterPartitions, 0, index);
228        }
229    }
230
231    // we have a set of systemPartitions. Reorder a preferred one to the front.
232    // if not, look for a preferred partition manually
233    // In the past, we just wanted to find one preferred partition, so
234    // addPreferredSystemPartitionInfo() would find one and put it at the front.
235    // But there is now a desire by some callers to be able to see all
236    // preferred ESPs.  So our logic becomes a little convoluted:
237    // Previously, we would look at any partitions on devices associated
238    // with the passed-in mountpoint.  If one of them was preferred, then
239    // make sure that one was first.  If not, then (and only then) call
240    // addPreferred..., which would (hopefully) find at most one preferred
241    // partition.
242    // With the new requirements we need to put them all in, which means we
243    // have to unconditionally call addPreferred....  But we don't want to
244    // have a behavior change for those callers who only look at the first element
245    // of the array; i.e. we still want the first one to be associated with the
246    // passed-in mountpoint if there's a preferred one there.
247    // So with that in mind, we'll continue to start out the same.  That is,
248    // look for a preferred ESP associated with the passed-in mountpoint and
249    // make sure it's first in the array.  Then, call addPreferred unconditionally.
250    // But addPreferred will have two changes: 1) Don't stop after finding the first
251    // one, so we get all of them, and 2) Only put a found preferred ESP at the front
252    // if there isn't already one there.
253
254    gotOne = false;
255    if (CFArrayGetCount(systemPartitions) > 0) {
256        CFIndex i, count;
257        count = CFArrayGetCount(systemPartitions);
258        for (i=0; i < count; i++) {
259            CFStringRef testSP = CFArrayGetValueAtIndex(systemPartitions, i);
260            if (isPreferredSystemPartition(context, testSP)) {
261                if (i > 0) CFArrayExchangeValuesAtIndices(systemPartitions, 0, i);
262                gotOne = true;
263                break;
264            }
265        }
266    }
267
268    addPreferredSystemPartitionInfo(context, systemPartitions, gotOne);
269
270    array = CFArrayCreateCopy(kCFAllocatorDefault, dataPartitions);
271    CFDictionaryAddValue(booters, kBLDataPartitionsKey, array);
272    CFRelease(array);
273    CFRelease(dataPartitions);
274
275    array = CFArrayCreateCopy(kCFAllocatorDefault, booterPartitions);
276    CFDictionaryAddValue(booters, kBLAuxiliaryPartitionsKey, array);
277    CFRelease(array);
278    CFRelease(booterPartitions);
279
280    array = CFArrayCreateCopy(kCFAllocatorDefault, systemPartitions);
281    CFDictionaryAddValue(booters, kBLSystemPartitionsKey, array);
282    CFRelease(array);
283    CFRelease(systemPartitions);
284
285	contextprintf(context, kBLLogLevelVerbose, "Returning booter information dictionary:\n%s\n",
286				  BLGetCStringDescription(booters));
287
288    *outDict = booters;
289
290    return 0;
291}
292
293static int addRAIDInfo(BLContextPtr context, CFDictionaryRef dict,
294                       CFMutableArrayRef dataPartitions,
295                       CFMutableArrayRef booterPartitions,
296                       CFMutableArrayRef systemPartitions)
297{
298    CFStringRef bootpath = NULL;
299    io_string_t iostring;
300    io_service_t    service;
301
302    int         ret;
303
304    bootpath = CFDictionaryGetValue(dict, CFSTR(kIOBootDevicePathKey));
305    if(bootpath == NULL || CFGetTypeID(bootpath) != CFStringGetTypeID()) {
306        contextprintf(context, kBLLogLevelError,  "Could not find boot path entry\n");
307        return 1;
308    }
309
310    if(!CFStringGetCString(bootpath,iostring,sizeof(iostring),kCFStringEncodingUTF8)) {
311        contextprintf(context, kBLLogLevelError,  "Invalid UTF8 for path entry\n");
312        return 2;
313    }
314
315    contextprintf(context, kBLLogLevelVerbose,  "Aggregate boot path is %s\n" , iostring);
316
317    service = IORegistryEntryFromPath(kIOMasterPortDefault, iostring );
318    if(service == IO_OBJECT_NULL) {
319        contextprintf(context, kBLLogLevelError,  "Could not find IOKit entry for %s\n" , iostring);
320        return 4;
321    }
322
323    ret = addDataPartitionInfo(context, service,
324                               dataPartitions,
325                               booterPartitions,
326                               systemPartitions);
327
328    IOObjectRelease(service);
329
330    return ret;
331}
332
333static int addDataPartitionInfo(BLContextPtr context, io_service_t dataPartition,
334                                CFMutableArrayRef dataPartitions,
335                                CFMutableArrayRef booterPartitions,
336                                CFMutableArrayRef systemPartitions)
337{
338    CFStringRef bsdName;
339    kern_return_t   kret;
340    io_service_t    parent;
341    uint32_t        partitionID, searchID;
342    CFNumberRef     partitionNum;
343    CFStringRef     content;
344    bool            needsBooter = false;
345    CFNumberRef     neededBooterPartitionNum = NULL;
346    CFStringRef     neededBooterContent = NULL;
347    CFStringRef     neededSystemContent = NULL;
348
349    io_iterator_t   childIterator;
350    io_service_t    child;
351
352    /* don't require this at this point
353    kret = IORegistryEntryGetPath(dataPartition, kIODeviceTreePlane, devPath);
354	if(kret != KERN_SUCCESS) {
355		contextprintf(context, kBLLogLevelError,  "Could not get path in device plane for service\n" );
356		return 1;
357	}
358    */
359
360    bsdName = IORegistryEntryCreateCFProperty(dataPartition, CFSTR(kIOBSDNameKey), kCFAllocatorDefault, 0);
361    if(bsdName == NULL || (CFGetTypeID(bsdName) != CFStringGetTypeID())) {
362        if(bsdName) CFRelease(bsdName);
363
364        return 1;
365    }
366
367    partitionNum = IORegistryEntryCreateCFProperty(dataPartition, CFSTR(kIOMediaPartitionIDKey), kCFAllocatorDefault, 0);
368    if(partitionNum == NULL || (CFGetTypeID(partitionNum) != CFNumberGetTypeID())) {
369        if(partitionNum) CFRelease(partitionNum);
370        CFRelease(bsdName);
371
372        return 1;
373    }
374
375    content = (CFStringRef)IORegistryEntryCreateCFProperty(dataPartition, CFSTR(kIOMediaContentKey), kCFAllocatorDefault, 0);
376    if(content == NULL || (CFGetTypeID(content) != CFStringGetTypeID())) {
377        if(content) CFRelease(content);
378		CFRelease(partitionNum);
379		CFRelease(bsdName);
380
381		contextprintf(context, kBLLogLevelError,  "Partition does not have Content key\n" );
382
383        return 1;
384    }
385
386
387    if(!CFNumberGetValue(partitionNum,kCFNumberSInt32Type, &partitionID)) {
388		CFRelease(content);
389		CFRelease(partitionNum);
390		CFRelease(bsdName);
391
392        contextprintf(context, kBLLogLevelError,  "Could not get Partition ID for service\n" );
393		return 1;
394    }
395
396    if(!CFArrayContainsValue(dataPartitions,CFRangeMake(0, CFArrayGetCount(dataPartitions)), bsdName)) {
397        CFArrayAppendValue(dataPartitions, bsdName);
398    }
399    CFRelease(bsdName);
400    CFRelease(partitionNum);
401
402    kret = IORegistryEntryGetParentEntry(dataPartition, kIOServicePlane, &parent);
403    if(kret != KERN_SUCCESS) {
404		CFRelease(content);
405
406        contextprintf(context, kBLLogLevelError,  "Could not get parent path in device plane for service\n" );
407		return 1;
408    }
409
410#if SUPPORT_APPLE_PARTITION_MAP
411    if(IOObjectConformsTo(parent, kIOApplePartitionSchemeClass)) {
412        contextprintf(context, kBLLogLevelVerbose,  "APM detected\n" );
413        // from the OS point of view, only it's an HFS or boot partition, it needs a booter
414
415        if(CFEqual(content, CFSTR("Apple_HFS"))  ||
416           CFEqual(content, CFSTR("Apple_HFSX")) ||
417           CFEqual(content, CFSTR("Apple_Boot")) ||
418           CFEqual(content, CFSTR("Apple_Boot_RAID")) ) {
419            needsBooter = false;
420        } else {
421            needsBooter = true;
422            searchID = partitionID - 1;
423            neededBooterContent = CFSTR("Apple_Boot");
424            neededBooterPartitionNum = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &searchID);
425        }
426
427    } else
428#endif // SUPPORT_APPLE_PARTITION_MAP
429	if(IOObjectConformsTo(parent, kIOGUIDPartitionSchemeClass)) {
430        contextprintf(context, kBLLogLevelVerbose,  "GPT detected\n" );
431
432        // from the OS point of view, only it's an HFS or boot partition, it needs a booter
433        if(CFEqual(content, CFSTR("48465300-0000-11AA-AA11-00306543ECAC"))  ||
434           CFEqual(content, CFSTR("426F6F74-0000-11AA-AA11-00306543ECAC"))) {
435            needsBooter = false;
436        } else {
437            needsBooter = true;
438            searchID = partitionID + 1;
439            neededBooterContent = CFSTR("426F6F74-0000-11AA-AA11-00306543ECAC");
440            neededBooterPartitionNum = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &searchID);
441        }
442
443        neededSystemContent = CFSTR("C12A7328-F81F-11D2-BA4B-00A0C93EC93B");
444
445    } else {
446        contextprintf(context, kBLLogLevelVerbose,  "Other partition scheme detected\n" );
447    }
448
449	CFRelease(content);
450
451
452    if(needsBooter) {
453        contextprintf(context, kBLLogLevelVerbose,  "Booter partition required at index %u\n", searchID);
454    } else {
455        contextprintf(context, kBLLogLevelVerbose,  "No auxiliary booter partition required\n");
456    }
457
458    if(needsBooter || neededSystemContent) {
459        kret = IORegistryEntryGetChildIterator(parent, kIOServicePlane, &childIterator);
460        if(kret != KERN_SUCCESS) {
461            contextprintf(context, kBLLogLevelError,  "Could not get child iterator for parent\n" );
462            return 4;
463        }
464
465        while((child = IOIteratorNext(childIterator)) != IO_OBJECT_NULL) {
466            CFStringRef childContent;
467
468            childContent = IORegistryEntryCreateCFProperty(child, CFSTR(kIOMediaContentKey), kCFAllocatorDefault, 0);
469            if(childContent && CFGetTypeID(childContent) == CFStringGetTypeID()) {
470                CFStringRef childBSDName;
471                // does it match
472                if(needsBooter && CFEqual(childContent, neededBooterContent)) {
473                    CFNumberRef childPartitionID = IORegistryEntryCreateCFProperty(child, CFSTR(kIOMediaPartitionIDKey), kCFAllocatorDefault, 0);
474
475                    if(childPartitionID && (CFGetTypeID(childPartitionID) == CFNumberGetTypeID()) && CFEqual(childPartitionID, neededBooterPartitionNum)) {
476                        childBSDName = IORegistryEntryCreateCFProperty(child, CFSTR(kIOBSDNameKey), kCFAllocatorDefault, 0);
477                        if(childBSDName && (CFGetTypeID(childBSDName) == CFStringGetTypeID())) {
478                            if(!CFArrayContainsValue(booterPartitions,CFRangeMake(0, CFArrayGetCount(booterPartitions)), childBSDName)) {
479                                CFArrayAppendValue(booterPartitions, childBSDName);
480                                contextprintf(context, kBLLogLevelVerbose,  "Booter partition found\n" );
481                            }
482                        }
483
484                        if (childBSDName) {
485                            CFRelease(childBSDName);
486                        }
487                    }
488                } else if(neededSystemContent && CFEqual(childContent, neededSystemContent)) {
489                    childBSDName = IORegistryEntryCreateCFProperty(child, CFSTR(kIOBSDNameKey), kCFAllocatorDefault, 0);
490                    if(childBSDName && (CFGetTypeID(childBSDName) == CFStringGetTypeID())) {
491                        if(!CFArrayContainsValue(systemPartitions,CFRangeMake(0, CFArrayGetCount(systemPartitions)), childBSDName)) {
492                            CFArrayAppendValue(systemPartitions, childBSDName);
493                            contextprintf(context, kBLLogLevelVerbose,  "System partition found\n" );
494                        }
495                    }
496
497                    if (childBSDName) {
498                        CFRelease(childBSDName);
499                    }
500                }
501            }
502
503            if(childContent) {
504                CFRelease(childContent);
505            }
506
507            IOObjectRelease(child);
508        }
509
510        IOObjectRelease(childIterator);
511
512    }
513
514    IOObjectRelease(parent);
515
516    return 0;
517}
518
519
520#ifndef kIOPropertyPhysicalInterconnectTypePCIExpress
521#define kIOPropertyPhysicalInterconnectTypePCIExpress	"PCI-Express"
522#endif
523
524static bool _isPreferredSystemPartition(BLContextPtr context, io_service_t service)
525{
526    CFDictionaryRef         protocolCharacteristics;
527    bool                    foundOne = false;
528
529    protocolCharacteristics = IORegistryEntrySearchCFProperty(service,
530                                                              kIOServicePlane,
531                                                              CFSTR(kIOPropertyProtocolCharacteristicsKey),
532                                                              kCFAllocatorDefault,
533                                                              kIORegistryIterateRecursively|
534                                                              kIORegistryIterateParents);
535
536    if(protocolCharacteristics && CFGetTypeID(protocolCharacteristics) == CFDictionaryGetTypeID()) {
537        CFStringRef interconnect = CFDictionaryGetValue(protocolCharacteristics,
538                                                        CFSTR(kIOPropertyPhysicalInterconnectTypeKey));
539        CFStringRef location = CFDictionaryGetValue(protocolCharacteristics,
540                                                    CFSTR(kIOPropertyPhysicalInterconnectLocationKey));
541
542        if(interconnect && location && CFGetTypeID(interconnect) == CFStringGetTypeID() && CFGetTypeID(location) == CFStringGetTypeID()) {
543            if(  (  CFEqual(interconnect,CFSTR(kIOPropertyPhysicalInterconnectTypeATA))
544                  || CFEqual(interconnect,CFSTR(kIOPropertyPhysicalInterconnectTypeSerialATA))
545				  || CFEqual(interconnect,CFSTR(kIOPropertyPhysicalInterconnectTypePCIExpress))
546				  || CFEqual(interconnect,CFSTR(kIOPropertyPhysicalInterconnectTypePCI)))
547               && CFEqual(location, CFSTR(kIOPropertyInternalKey))) {
548                // OK, found an internal ESP
549                foundOne = true;
550            }
551
552        }
553
554    }
555    if(protocolCharacteristics) CFRelease(protocolCharacteristics);
556
557    return foundOne;
558
559}
560
561bool isPreferredSystemPartition(BLContextPtr context, CFStringRef bsdName)
562{
563    CFMutableDictionaryRef  matching;
564    io_service_t            service;
565    bool                    ret;
566
567    matching = IOServiceMatching(kIOMediaClass);
568    CFDictionarySetValue(matching, CFSTR(kIOBSDNameKey), bsdName);
569
570
571    service = IOServiceGetMatchingService(kIOMasterPortDefault,
572                                          matching);
573
574    if(service == IO_OBJECT_NULL) {
575        return false;
576    }
577
578    ret = _isPreferredSystemPartition(context, service);
579
580    IOObjectRelease(service);
581
582    return ret;
583}
584
585
586// search for all ESPs on the system. Once we find them, only add internal
587// interconnects on a SATA/PATA/PCI bus
588// See comments in BLCreateBooterInformationDictionary for the odd logic
589// of where any found preferred ESPs go.  We find *all* of them and don't just
590// quit after one.
591static int addPreferredSystemPartitionInfo(BLContextPtr context,
592                                           CFMutableArrayRef systemPartitions,
593                                           bool foundPreferred)
594{
595
596    CFMutableDictionaryRef  matching;
597    kern_return_t           kret;
598    io_iterator_t           iter;
599    io_service_t            service;
600    CFStringRef             bsdName;
601
602    matching = IOServiceMatching(kIOMediaClass);
603    CFDictionarySetValue(matching, CFSTR(kIOMediaContentKey), CFSTR("C12A7328-F81F-11D2-BA4B-00A0C93EC93B"));
604
605    kret = IOServiceGetMatchingServices(kIOMasterPortDefault, matching, &iter);
606    if (kret != KERN_SUCCESS) {
607        contextprintf(context, kBLLogLevelVerbose,  "No preferred system partitions found\n" );
608        return 0;
609    }
610
611    while ((service = IOIteratorNext(iter)) != IO_OBJECT_NULL) {
612        if (_isPreferredSystemPartition(context, service)) {
613            bsdName = IORegistryEntryCreateCFProperty(service, CFSTR(kIOBSDNameKey), kCFAllocatorDefault, 0);
614            if (bsdName && (CFGetTypeID(bsdName) == CFStringGetTypeID())) {
615                contextprintf(context, kBLLogLevelVerbose,  "Preferred system partition found: %s\n", BLGetCStringDescription(bsdName));
616                if (CFArrayGetFirstIndexOfValue(systemPartitions, CFRangeMake(0, CFArrayGetCount(systemPartitions)), bsdName) == kCFNotFound) {
617                    // this is a new preferred ESP. If there isn't already a preferred one in the array,
618                    // put this one at the front.  Otherwise put it second.
619                    CFArrayInsertValueAtIndex(systemPartitions, foundPreferred ? 1 : 0, bsdName);
620                    foundPreferred = true;
621                }
622            }
623            if (bsdName) CFRelease(bsdName);
624            IOObjectRelease(service);
625        }
626        IOObjectRelease(service);
627    }
628    IOObjectRelease(iter);
629
630    return 0;
631}
632
633