1/*
2 * Copyright (c) 2001-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 *  BLGetOpenFirmwareBootDevice.c
25 *  bless
26 *
27 *  Created by Shantonu Sen <ssen@apple.com> on Thu Apr 19 2001.
28 *  Copyright (c) 2001-2007 Apple Inc. All Rights Reserved.
29 *
30 *  $Id: BLGetOpenFirmwareBootDevice.c,v 1.30 2006/02/20 22:49:57 ssen Exp $
31 *
32 */
33#include <stdlib.h>
34#include <stdio.h>
35#include <string.h>
36#include <unistd.h>
37#include <fcntl.h>
38#include <sys/stat.h>
39#include <sys/param.h>
40
41#import <mach/mach_error.h>
42
43#import <IOKit/IOKitLib.h>
44#import <IOKit/IOBSD.h>
45#import <IOKit/storage/IOMedia.h>
46#import <IOKit/storage/IOMediaBSDClient.h>
47#import <IOKit/storage/IOPartitionScheme.h>
48
49#import <CoreFoundation/CoreFoundation.h>
50
51#include "bless.h"
52#include "bless_private.h"
53
54int getPNameAndPType(BLContextPtr context,
55                            char * target,
56			    char * pname,
57			    char * ptype);
58
59int getExternalBooter(BLContextPtr context,
60                      mach_port_t iokitPort,
61					  io_service_t dataPartition,
62 					 io_service_t *booterPartition);
63
64/*
65 * Get OF string for device
66 * If it's a non-whole device
67 *   Look for an external booter
68 *     If there is, replace the partition number in OF string
69 * For new world, add a tbxi
70 */
71
72int BLGetOpenFirmwareBootDevice(BLContextPtr context, const char * mntfrm, char * ofstring) {
73
74    int err;
75
76    kern_return_t           kret;
77    mach_port_t             ourIOKitPort;
78    io_service_t            service;
79
80	int32_t					needsBooter	= 0;
81	int32_t					isBooter	= 0;
82
83    io_string_t				ofpath;
84    char			device[MAXPATHLEN];
85	char *split = ofpath;
86	char tbxi[5];
87
88    CFTypeRef               bootData = NULL;
89
90    if(!mntfrm || 0 != strncmp(mntfrm, "/dev/", 5)) return 1;
91
92	strlcpy(device, mntfrm, sizeof device);
93
94    // Obtain the I/O Kit communication handle.
95    if((kret = IOMasterPort(bootstrap_port, &ourIOKitPort)) != KERN_SUCCESS) {
96      return 2;
97    }
98
99#if SUPPORT_RAID
100    err = BLGetRAIDBootDataForDevice(context, mntfrm, &bootData);
101    if(err) {
102        contextprintf(context, kBLLogLevelError,  "Error while determining if %s is a RAID\n", mntfrm );
103        return 3;
104    }
105#endif
106
107    if(bootData) {
108        CFDictionaryRef primary = NULL;
109        CFStringRef bootpath = NULL;
110		CFStringRef name = NULL;
111        io_string_t iostring;
112
113        // update name with the primary partition
114        if(CFGetTypeID(bootData) == CFArrayGetTypeID() ) {
115			if(CFArrayGetCount(bootData) == 0) {
116				contextprintf(context, kBLLogLevelError,  "RAID set has no bootable members\n" );
117				return 3;
118			}
119
120            primary = CFArrayGetValueAtIndex(bootData,0);
121            CFRetain(primary);
122        } else if(CFGetTypeID(bootData) == CFDictionaryGetTypeID()) {
123            primary = bootData;
124            CFRetain(primary);
125        }
126
127        bootpath = CFDictionaryGetValue(primary, CFSTR(kIOBootDevicePathKey));
128        if(bootpath == NULL || CFGetTypeID(bootpath) != CFStringGetTypeID()) {
129            CFRelease(primary);
130            CFRelease(bootData);
131            contextprintf(context, kBLLogLevelError,  "Could not find boot path entry for %s\n" , mntfrm);
132            return 4;
133        }
134
135        if(!CFStringGetCString(bootpath,iostring,sizeof(iostring),kCFStringEncodingUTF8)) {
136            CFRelease(primary);
137            CFRelease(bootData);
138            contextprintf(context, kBLLogLevelError,  "Invalid UTF8 for path entry for %s\n" , mntfrm);
139            return 4;
140        }
141
142		contextprintf(context, kBLLogLevelVerbose,  "Primary OF boot path is %s\n" , iostring);
143
144        service = IORegistryEntryFromPath(ourIOKitPort, iostring );
145        if(service == 0) {
146            CFRelease(primary);
147            CFRelease(bootData);
148            contextprintf(context, kBLLogLevelError,  "Could not find IOKit entry for %s\n" , iostring);
149            return 4;
150        }
151
152        CFRelease(primary);
153        CFRelease(bootData);
154
155		name = IORegistryEntryCreateCFProperty( service, CFSTR(kIOBSDNameKey),
156												kCFAllocatorDefault, 0);
157
158		if(name == NULL || CFStringGetTypeID() != CFGetTypeID(name)) {
159			IOObjectRelease(service);
160            contextprintf(context, kBLLogLevelError,  "Could not find bsd name for %s\n" , iostring);
161			return 5;
162		}
163
164		IOObjectRelease(service); service = 0;
165
166		if(!CFStringGetCString(name,device+5,sizeof(iostring)-5,kCFStringEncodingUTF8)) {
167			CFRelease(name);
168            contextprintf(context, kBLLogLevelError,  "Could not find bsd name for %s\n" , iostring);
169			return 5;
170		}
171
172		CFRelease(name);
173    }
174
175    // by this point, "service" should point at the data partition, or potentially
176    // a RAID member. We'll need to map it to a booter partition if necessary
177
178	err = BLDeviceNeedsBooter(context, device,
179							  &needsBooter,
180							  &isBooter,
181							  &service);
182	if(err) {
183		contextprintf(context, kBLLogLevelError,  "Could not determine if partition needs booter\n" );
184		return 10;
185	}
186
187	if(!needsBooter && !isBooter) {
188		err = BLGetIOServiceForDeviceName(context, (char *)device + 5, &service);
189		if(err) {
190			contextprintf(context, kBLLogLevelError,  "Can't find IOService for %s\n", device + 5 );
191			return 10;
192		}
193
194	}
195
196	kret = IORegistryEntryGetPath(service, kIODeviceTreePlane, ofpath);
197	if(kret != KERN_SUCCESS) {
198		contextprintf(context, kBLLogLevelError,  "Could not get path in device plane for service\n" );
199		IOObjectRelease(service);
200		return 11;
201	}
202
203	IOObjectRelease(service);
204
205	split = ofpath;
206
207	strsep(&split, ":");
208
209	if(split == NULL) {
210		contextprintf(context, kBLLogLevelError,  "Bad path in device plane for service\n" );
211		IOObjectRelease(service);
212		return 11;
213	}
214
215	sprintf(ofstring, "%s,\\\\:%s", split, blostype2string(kBL_OSTYPE_PPC_TYPE_BOOTX, tbxi));
216
217    return 0;
218}
219
220
221
222int getPNameAndPType(BLContextPtr context,
223                     char * target,
224		     char * pname,
225		     char * ptype) {
226
227  kern_return_t       status;
228  mach_port_t         ourIOKitPort;
229  io_iterator_t       services;
230  io_registry_entry_t obj;
231    CFStringRef temp = NULL;
232
233  pname[0] = '\0';
234  ptype[0] = '\0';
235
236  // Obtain the I/O Kit communication handle.
237  status = IOMasterPort(bootstrap_port, &ourIOKitPort);
238  if (status != KERN_SUCCESS) {
239    return 1;
240  }
241
242  // Obtain our list of one object
243  // +5 to skip over "/dev/"
244  status = IOServiceGetMatchingServices(ourIOKitPort,
245					IOBSDNameMatching(ourIOKitPort,
246							  0,
247							  target),
248					&services);
249  if (status != KERN_SUCCESS) {
250    return 2;
251  }
252
253  // Should only be one IOKit object for this volume. (And we only want one.)
254  obj = IOIteratorNext(services);
255  if (!obj) {
256    return 3;
257  }
258
259    temp = (CFStringRef)IORegistryEntryCreateCFProperty(
260	obj,
261	CFSTR(kIOMediaContentKey),
262        kCFAllocatorDefault,
263	0);
264
265  if (temp == NULL) {
266    return 4;
267  }
268
269    if(!CFStringGetCString(temp, ptype, MAXPATHLEN, kCFStringEncodingMacRoman)) {
270        CFRelease(temp);
271        return 4;
272    }
273
274    CFRelease(temp);
275
276    status = IORegistryEntryGetName(obj,pname);
277  if (status != KERN_SUCCESS) {
278    return 5;
279  }
280
281
282
283  IOObjectRelease(obj);
284  obj = 0;
285
286  IOObjectRelease(services);
287  services = 0;
288
289    contextprintf(context, kBLLogLevelVerbose,  "looking at partition %s, type %s, name %s\n", target, ptype, pname );
290
291  return 0;
292
293}
294
295int getExternalBooter(BLContextPtr context,
296                      mach_port_t iokitPort,
297					  io_service_t dataPartition,
298					  io_service_t *booterPartition)
299{
300	CFStringRef name = NULL;
301	CFStringRef content = NULL;
302	char cname[MAXPATHLEN];
303	char *spos = NULL;
304	io_service_t booter = 0;
305	int partnum = 0;
306	int errnum;
307
308	name = (CFStringRef)IORegistryEntryCreateCFProperty(
309														dataPartition,
310														CFSTR(kIOBSDNameKey),
311														kCFAllocatorDefault,
312														0);
313
314	if(!CFStringGetCString(name, cname, sizeof(cname), kCFStringEncodingUTF8)) {
315        CFRelease(name);
316        return 1;
317    }
318
319	CFRelease(name);
320
321	spos = strrchr(cname, 's');
322	if(spos == NULL || spos == &cname[2]) {
323		contextprintf(context, kBLLogLevelError,  "Can't determine partition for %s\n", cname );
324		return 2;
325	}
326
327	partnum = atoi(spos+1);
328	sprintf(spos, "s%d", partnum-1);
329
330	errnum = BLGetIOServiceForDeviceName(context, cname, &booter);
331	if(errnum) {
332		contextprintf(context, kBLLogLevelError,  "Could not find IOKit entry for %s\n" , cname);
333		return 4;
334	}
335
336	content = (CFStringRef)IORegistryEntryCreateCFProperty(
337														booter,
338														CFSTR(kIOMediaContentKey),
339														kCFAllocatorDefault,
340														0);
341	if(content == NULL || CFGetTypeID(content) != CFStringGetTypeID()) {
342		contextprintf(context, kBLLogLevelError,  "Invalid content type for %s\n" , cname);
343		IOObjectRelease(booter);
344		return 5;
345	}
346
347	if(!CFEqual(CFSTR("Apple_Boot"), content)) {
348		contextprintf(context, kBLLogLevelError,  "Booter partition %s is not Apple_Boot\n" , cname);
349		IOObjectRelease(booter);
350		return 6;
351	}
352
353	*booterPartition = booter;
354	return 0;
355}
356