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 *  handleInfo.c
25 *  bless
26 *
27 *  Created by Shantonu Sen <ssen@apple.com> on Thu Dec 6 2001.
28 *  Copyright (c) 2001-2007 Apple Inc. All Rights Reserved.
29 *
30 *  $Id: handleInfo.c,v 1.45 2006/06/24 00:46:37 ssen Exp $
31 *
32 *
33 */
34
35#include <stdlib.h>
36#include <stdio.h>
37#include <unistd.h>
38#include <string.h>
39
40#include <sys/stat.h>
41#include <sys/mount.h>
42#include <sys/param.h>
43
44#include <sys/socket.h>
45#include <net/if.h>
46#include <arpa/nameser.h>
47#include <netinet/in.h>
48#include <arpa/inet.h>
49
50#include "enums.h"
51#include "structs.h"
52
53#include "bless.h"
54#include "bless_private.h"
55#include "protos.h"
56
57#include <IOKit/storage/IOMedia.h>
58#include <IOKit/IOBSD.h>
59#include <CoreFoundation/CoreFoundation.h>
60
61static int interpretEFIString(BLContextPtr context, CFStringRef efiString,
62                              char *bootdevice);
63
64static void addElements(const void *key, const void *value, void *context);
65
66static int findBootRootAggregate(BLContextPtr context, char *memberPartition, char *bootRootDevice, int deviceLen);
67
68
69
70/* 8 words of "finder info" in volume
71 * 0 & 1 often set to blessed system folder
72 * boot blocks contain name of system file, name of shell app, and startup app
73 * starting w/sys8 added file OpenFolderListDF ... which wins open at mount
74 * there are per-file/folder "finder info" fields such as invis, location, etc
75 * "next-folder in "open folder list" chain ... first item came from word 2
76 * 3 & 5 co-opted in the X timeframe to store dirID's of dual-install sysF's
77 * 6 & 7 is the vsdb stuff (64 bits) to see if sysA has seen diskB
78 *
79 * 0 is blessed system folder
80 * 1 is blessed system file for EFI systems
81 * 2 is first link in linked list of folders to open at mount (deprecated)
82 *   (9 and X are supposed to honor this if set and ignore OpenFolderListDF)
83 * 3 OS 9 blessed system folder (maybe OS X?)
84 * 4 thought to be unused (reserved for Finder, once was PowerTalk Inbox)
85 * 5 OS X blessed system folder (maybe OS 9?)
86 * 6 & 7 are 64 bit volume identifier (high 32 bits in 6; low in 7)
87 */
88
89#define MISSINGMSG "<missing>"
90static const char *messages[7][2] = {
91
92    { "No Blessed System Folder", "Blessed System Folder is " },    /* 0 */
93    { "No Blessed System File", "Blessed System File is " },
94    { "Open-folder linked list empty", "1st dir in open-folder list is " },
95    { "No alternate OS blessed file/folder", "Alternate OS blessed file/folder is " },  /* 3 */
96    { "Unused field unset", "Thought-to-be-unused field points to " },
97    { "No OS 9 + X blessed X folder", "OS X blessed folder is " },  /* 5 */
98    { "64-bit VSDB volume id not present", "64-bit VSDB volume id: " }
99
100};
101
102int modeInfo(BLContextPtr context, struct clarg actargs[klast]) {
103    int                 ret;
104    CFDictionaryRef     dict;
105    struct statfs       sb;
106    CFMutableDictionaryRef  allInfo = NULL;
107	int					isHFS = 0;
108
109    if(!actargs[kinfo].hasArg || actargs[kgetboot].present) {
110        char currentString[1024];
111        char currentDev[1024]; // may contain URLs like bsdp://foo
112        BLPreBootEnvType	preboot;
113
114        ret = BLGetPreBootEnvironmentType(context, &preboot);
115        if(ret)
116            return 1;
117
118        if (preboot == kBLPreBootEnvType_EFI) {
119            CFStringRef     efibootdev = NULL;
120
121            ret = BLCopyEFINVRAMVariableAsString(context,
122                                                 CFSTR("efi-boot-device"),
123                                                 &efibootdev);
124
125            if(ret || efibootdev == NULL) {
126                blesscontextprintf(context, kBLLogLevelError,
127                                   "Can't access \"efi-boot-device\" NVRAM variable\n");
128                return 1;
129            }
130
131            blesscontextprintf(context, kBLLogLevelVerbose,  "Current EFI boot device string is: '%s'\n",
132                               BLGetCStringDescription(efibootdev));
133
134            ret = BLValidateXMLBootOption(context,
135                                          CFSTR("efi-boot-device"),
136                                          CFSTR("efi-boot-device-data"));
137            if(ret) {
138                CFRelease(efibootdev);
139                blesscontextprintf(context, kBLLogLevelError,
140                                   "XML representation doesn't match true boot preference\n");
141                return 2;
142            }
143
144            ret = interpretEFIString(context, efibootdev, currentDev);
145            if(ret) {
146                CFRelease(efibootdev);
147                blesscontextprintf(context, kBLLogLevelError,
148                                   "Can't interpet EFI boot device\n");
149                return 2;
150            }
151
152            CFRelease(efibootdev);
153        } else {
154            blesscontextprintf(context, kBLLogLevelError,  "Unknown preboot environment\n");
155            return 1;
156        }
157
158        // Check for Boot!=Root for physical disk boot paths
159        if (0 == strncmp(currentDev, "/dev/", 5)) {
160            char parentDev[MNAMELEN];
161            uint32_t partNum;
162            BLPartitionType mapType;
163            io_service_t service = IO_OBJECT_NULL;
164            CFStringRef contentHint;
165
166            ret = BLGetIOServiceForDeviceName(context, currentDev + 5, &service);
167            if (ret) {
168                blesscontextprintf(context, kBLLogLevelError,
169                                   "Can't get IOService for %s\n", currentDev);
170                return 3;
171            }
172
173            contentHint = IORegistryEntryCreateCFProperty(service, CFSTR(kIOMediaContentKey), kCFAllocatorDefault, 0);
174
175            if (contentHint && CFGetTypeID(contentHint) == CFStringGetTypeID()) {
176                bool doSearch = false;
177                char booterPart[MNAMELEN];
178
179                ret = BLGetParentDeviceAndPartitionType(context, currentDev,
180                                                        parentDev,
181                                                        &partNum,
182                                                        &mapType);
183                if (ret) {
184                    blesscontextprintf(context, kBLLogLevelError,
185                                       "Can't determine parent media for %s\n", currentDev);
186                    return 3;
187                }
188
189                switch(mapType) {
190                    case kBLPartitionType_APM:
191                        if (CFEqual(contentHint, CFSTR("Apple_Boot"))) {
192                            doSearch = true;
193                            snprintf(booterPart, sizeof(booterPart), "%ss%u", parentDev, partNum+1);
194                        }
195                        break;
196                    case kBLPartitionType_GPT:
197                        if (CFEqual(contentHint, CFSTR("426F6F74-0000-11AA-AA11-00306543ECAC"))) {
198                            doSearch = true;
199                            snprintf(booterPart, sizeof(booterPart), "%ss%u", parentDev, partNum-1);
200                        }
201                        break;
202                    default:
203                        blesscontextprintf(context, kBLLogLevelVerbose,
204                                           "Partition map type does not support Boot!=Root\n");
205                        break;
206                }
207
208                if (doSearch) {
209                    ret = findBootRootAggregate(context, booterPart, currentDev, sizeof currentDev);
210                    if (ret) {
211                        blesscontextprintf(context, kBLLogLevelError,
212                                           "Failed to find Boot!=Root aggregate media for %s\n", currentDev);
213                        return 3;
214                    }
215                }
216            }
217
218
219            if (contentHint) CFRelease(contentHint);
220            IOObjectRelease(service);
221
222        }
223
224	    if( actargs[kgetboot].present) {
225            if(actargs[kplist].present) {
226                CFStringRef vol = CFStringCreateWithCString(kCFAllocatorDefault,
227                                                            currentDev,
228                                                            kCFStringEncodingUTF8);
229                CFMutableDictionaryRef dict = NULL;
230                CFDataRef		tempData = NULL;
231
232                dict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
233                                                 &kCFTypeDictionaryKeyCallBacks,
234                                                 &kCFTypeDictionaryValueCallBacks);
235                CFDictionaryAddValue(dict, CFSTR("Boot Volume"), vol);
236                CFRelease(vol);
237
238                tempData = CFPropertyListCreateXMLData(kCFAllocatorDefault, dict);
239
240                write(fileno(stdout), CFDataGetBytePtr(tempData), CFDataGetLength(tempData));
241
242                CFRelease(tempData);
243                CFRelease(dict);
244
245            } else {
246                blesscontextprintf(context, kBLLogLevelNormal, "%s\n", currentDev);
247            }
248            return 0;
249	    }
250
251        // only look at mountpoints if it looks like a dev node
252        if (0 == strncmp(currentDev, "/dev/", 5)) {
253            struct statfs *mnts;
254            int vols;
255
256            vols = getmntinfo(&mnts, MNT_NOWAIT);
257            if(vols == -1) {
258                blesscontextprintf(context, kBLLogLevelError,  "Error gettings mounts\n" );
259                return 1;
260            }
261
262            while(--vols >= 0) {
263                struct statfs sb;
264
265                // somewhat redundant, but blsustatfs will canonicalize the mount device
266                if(0 != blsustatfs(mnts[vols].f_mntonname, &sb)) {
267                    continue;
268                }
269
270                if(strncmp(sb.f_mntfromname, currentDev, strlen(currentDev)+1) == 0) {
271                    blesscontextprintf(context, kBLLogLevelVerbose,  "mount: %s\n", mnts[vols].f_mntonname );
272                    strlcpy(actargs[kinfo].argument, mnts[vols].f_mntonname, kMaxArgLength);
273                    actargs[kinfo].hasArg = 1;
274                    break;
275                }
276
277            }
278        }
279
280	    if(!actargs[kinfo].hasArg) {
281            blesscontextprintf(context, kBLLogLevelError,
282                               "Volume for path %s is not available\n",
283                               currentString);
284            return 2;
285	    }
286    }
287
288
289    ret = BLGetCommonMountPoint(context, actargs[kinfo].argument, "", actargs[kmount].argument);
290    if(ret) {
291        blesscontextprintf(context, kBLLogLevelError,  "Can't get mount point for %s\n", actargs[kinfo].argument );
292        return 1;
293    }
294
295    ret = blsustatfs(actargs[kmount].argument, &sb);
296    if(ret) {
297        blesscontextprintf(context, kBLLogLevelError,  "Can't get device for %s\n", actargs[kmount].argument );
298        return 1;
299
300    }
301
302	ret = BLIsMountHFS(context, actargs[kmount].argument, &isHFS);
303    if(ret) {
304		blesscontextprintf(context, kBLLogLevelError,  "Could not determine filesystem of %s\n", actargs[kmount].argument );
305		return 1;
306    }
307
308	if(isHFS) {
309		ret = BLCreateVolumeInformationDictionary(context, actargs[kmount].argument,
310												  &dict);
311		if(ret) {
312			blesscontextprintf(context, kBLLogLevelError,  "Can't print Finder information\n" );
313			return 1;
314		}
315
316		allInfo = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, dict);
317		CFRelease(dict);
318		dict = NULL;
319	} else {
320		allInfo = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
321	}
322
323
324    ret = BLCreateBooterInformationDictionary(context, sb.f_mntfromname + 5, &dict);
325    if(ret) {
326        blesscontextprintf(context, kBLLogLevelError,  "Can't get booter information\n" );
327		return 3;
328    }
329
330    CFDictionaryApplyFunction(dict, addElements, (void *)allInfo);
331    CFRelease(dict);
332
333    dict = (CFDictionaryRef)allInfo;
334
335    if(actargs[kplist].present) {
336        CFDataRef		tempData = NULL;
337
338        tempData = CFPropertyListCreateXMLData(kCFAllocatorDefault, dict);
339
340        write(fileno(stdout), CFDataGetBytePtr(tempData), CFDataGetLength(tempData));
341
342        CFRelease(tempData);
343
344    } else if(isHFS) {
345        CFArrayRef finfo = CFDictionaryGetValue(dict, CFSTR("Finder Info"));
346        int j;
347        CFNumberRef vsdbref;
348        uint64_t vsdb;
349
350        for(j = 0; j < (8-2); j++) {
351            CFDictionaryRef word = CFArrayGetValueAtIndex(finfo, j);
352            CFNumberRef dirID = CFDictionaryGetValue(word, CFSTR("Directory ID"));
353            CFStringRef path = CFDictionaryGetValue(word, CFSTR("Path"));
354            uint32_t dirint;
355            char cpath[MAXPATHLEN];
356
357            if(!CFNumberGetValue(dirID, kCFNumberSInt32Type, &dirint)) {
358                continue;
359            }
360
361            if(dirint > 0 && CFStringGetLength(path) == 0) {
362                strlcpy(cpath, MISSINGMSG, sizeof cpath);
363            } else {
364                if(!CFStringGetCString(path, cpath, MAXPATHLEN, kCFStringEncodingUTF8)) {
365                    continue;
366                }
367            }
368
369            blesscontextprintf(context, kBLLogLevelNormal,
370                               "finderinfo[%i]: %6u => %s%s\n", j, dirint,
371                               messages[j][dirint > 0], cpath);
372
373        }
374
375
376        vsdbref = CFDictionaryGetValue(dict, CFSTR("VSDB ID"));
377        CFNumberGetValue(vsdbref, kCFNumberSInt64Type, &vsdb);
378
379
380    	blesscontextprintf(context, kBLLogLevelNormal, "%s 0x%016llX\n", messages[6][1],
381                           vsdb);
382
383    }
384
385	CFRelease(dict);
386
387    return 0;
388}
389
390static int interpretEFIString(BLContextPtr context, CFStringRef efiString,
391                              char *bootdevice)
392{
393    char                interface[IF_NAMESIZE];
394    char                host[NS_MAXDNAME];
395    char                path[1024];
396    int                 ret;
397
398    ret = BLInterpretEFIXMLRepresentationAsDevice(context,
399                                                  efiString,
400                                                  path,
401                                                  sizeof path);
402    if(ret == 0) {
403        blesscontextprintf(context, kBLLogLevelVerbose,  "Disk boot device detected\n" );
404
405        sprintf(bootdevice, "/dev/%s", path);
406
407        return 0;
408    } else {
409		BLNetBootProtocolType protocol;
410
411        ret = BLInterpretEFIXMLRepresentationAsNetworkPath(context,
412                                                           efiString,
413														   &protocol,
414                                                           interface,
415                                                           host,
416                                                           path);
417
418        if(ret == 0) {
419            blesscontextprintf(context, kBLLogLevelVerbose,  "Network boot device detected\n" );
420
421            if(strlen(path) > 0) {
422                sprintf(bootdevice, "tftp://%s@%s/%s", interface, host, path);
423            } else {
424                sprintf(bootdevice, "%s://%s@%s",
425                                        (protocol == kBLNetBootProtocol_PXE ? "pxe" : "bsdp"),
426                                        interface, host);
427            }
428
429            return 0;
430        } else {
431			ret = BLInterpretEFIXMLRepresentationAsLegacyDevice(context,
432                                                                efiString,
433                                                                path,
434                                                                sizeof path);
435			if(ret == 0) {
436				blesscontextprintf(context, kBLLogLevelVerbose,  "Legacy boot device detected\n" );
437
438				sprintf(bootdevice, "/dev/%s", path);
439
440				return 0;
441			}
442		}
443    }
444
445    blesscontextprintf(context, kBLLogLevelError,  "Could not interpret boot device as either network or disk\n" );
446
447    return 1;
448}
449
450// stolen from BLGetDeviceForOFPath
451static int isBootRootPath(BLContextPtr context, mach_port_t iokitPort, io_service_t member,
452					  CFDictionaryRef raidEntry)
453{
454	CFStringRef path;
455	io_string_t	cpath;
456	io_service_t	service;
457
458	path = CFDictionaryGetValue(raidEntry, CFSTR(kIOBootDevicePathKey));
459	if(path == NULL) return 0;
460
461	if(!CFStringGetCString(path,cpath,sizeof(cpath),kCFStringEncodingUTF8))
462		return 0;
463
464	contextprintf(context, kBLLogLevelVerbose,  "Comparing member to %s", cpath);
465
466	service = IORegistryEntryFromPath(iokitPort, cpath);
467	if(service == 0) {
468		contextprintf(context, kBLLogLevelVerbose,  "\nCould not find service\n");
469		return 0;
470	}
471
472	if(IOObjectIsEqualTo(service, member)) {
473		contextprintf(context, kBLLogLevelVerbose,  "\tEQUAL\n");
474		IOObjectRelease(service);
475		return 1;
476	} else {
477		contextprintf(context, kBLLogLevelVerbose,  "\tNOT EQUAL\n");
478	}
479
480	IOObjectRelease(service);
481
482	return 0;
483}
484
485
486
487static int findBootRootAggregate(BLContextPtr context, char *memberPartition, char *bootRootDevice, int deviceLen)
488{
489    io_service_t member = IO_OBJECT_NULL, testmedia = IO_OBJECT_NULL;
490    io_iterator_t iter;
491    kern_return_t kret;
492    int ret;
493    bool                foundBootRoot = false;
494    CFStringRef         memberContent = NULL;
495
496    ret = BLGetIOServiceForDeviceName(context, memberPartition + 5, &member);
497    if (ret) {
498        blesscontextprintf(context, kBLLogLevelError,
499                           "Can't get IOService for %s\n", memberPartition);
500        return 3;
501    }
502
503    kret = IOServiceGetMatchingServices(kIOMasterPortDefault, IOServiceMatching(kIOMediaClass), &iter);
504	if(kret != KERN_SUCCESS) {
505        IOObjectRelease(member);
506		contextprintf(context, kBLLogLevelVerbose,  "Could not find any media devices on the system\n");
507		return 2;
508	}
509
510    while((testmedia = IOIteratorNext(iter))) {
511		CFTypeRef			data = NULL;
512        io_string_t         iopath;
513
514        if(0 == IORegistryEntryGetPath(testmedia, kIOServicePlane, iopath)) {
515            contextprintf(context, kBLLogLevelVerbose,  "Checking %s\n", iopath);
516        }
517
518        data = IORegistryEntrySearchCFProperty( testmedia,
519                                               kIOServicePlane,
520                                               CFSTR(kIOBootDeviceKey),
521                                               kCFAllocatorDefault,
522                                               kIORegistryIterateRecursively|
523                                               kIORegistryIterateParents);
524
525        if(data) {
526            if (CFGetTypeID(data) == CFArrayGetTypeID()) {
527                CFIndex i, count = CFArrayGetCount(data);
528                for(i=0; i < count; i++) {
529                    CFDictionaryRef ent = CFArrayGetValueAtIndex((CFArrayRef)data,i);
530                    if(isBootRootPath(context, kIOMasterPortDefault, member, ent)) {
531                        foundBootRoot = true;
532                        break;
533                    }
534                }
535
536            } else if(CFGetTypeID(data) == CFDictionaryGetTypeID()) {
537                if(isBootRootPath(context, kIOMasterPortDefault, member, (CFDictionaryRef)data)) {
538                    foundBootRoot = true;
539                }
540            }
541            CFRelease(data);
542		}
543
544        if (foundBootRoot) {
545            CFStringRef bsdName;
546
547            bsdName = IORegistryEntryCreateCFProperty(testmedia, CFSTR(kIOBSDNameKey), kCFAllocatorDefault, 0);
548
549            contextprintf(context, kBLLogLevelVerbose,  "Found Boot!=Root aggregate media %s\n", BLGetCStringDescription(bsdName));
550
551            strlcpy(bootRootDevice, "/dev/", deviceLen);
552            CFStringGetCString(bsdName, bootRootDevice+5, 1024-5, kCFStringEncodingUTF8);
553
554            CFRelease(bsdName);
555
556        }
557
558        IOObjectRelease(testmedia);
559
560        if (foundBootRoot) break;
561    }
562    IOObjectRelease(iter);
563
564    memberContent = IORegistryEntryCreateCFProperty(member, CFSTR(kIOMediaContentKey), kCFAllocatorDefault, 0);
565    IOObjectRelease(member);
566
567
568    // not boot root. It might still be something like UFS
569    if (!foundBootRoot) {
570        if (memberContent && (!CFEqual(memberContent, CFSTR("Apple_Boot")) && !CFEqual(memberContent, CFSTR("Apple_HFS")))) {
571            foundBootRoot = true;
572            contextprintf(context, kBLLogLevelVerbose,  "Found legacy Apple_Boot media %s\n", BLGetCStringDescription(memberContent));
573
574            strlcpy(bootRootDevice, memberPartition, deviceLen);
575        }
576    }
577
578    if (memberContent) CFRelease(memberContent);
579
580    if (foundBootRoot) {
581        return 0;
582    } else {
583        return 1;
584    }
585}
586
587static void addElements(const void *key, const void *value, void *context)
588{
589    CFMutableDictionaryRef dict = (CFMutableDictionaryRef)context;
590
591    CFDictionaryAddValue(dict, key, value);
592}
593