1/*
2 * Copyright (c) 2004 Apple Computer, 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#include "AppleFileSystemDriver.h"
25
26#include <IOKit/IOLib.h>
27#include <IOKit/IOBSD.h>
28#include <IOKit/IOBufferMemoryDescriptor.h>
29#include "AppleRAIDUserLib.h"
30
31#include <libkern/OSByteOrder.h>
32
33#include <sys/param.h>
34#include <hfs/hfs_format.h>
35#include <libkern/crypto/md5.h>
36#include <uuid/uuid.h>
37#include <uuid/namespace.h>
38
39//------------------------------------------
40
41//#define VERBOSE 1
42//#define DEBUG 1
43#if DEBUG
44#define VERBOSE 1
45#define DEBUG_LOG(fmt, args...)  IOLog(fmt, ## args)
46#else
47#define DEBUG_LOG(fmt, args...)
48#endif
49
50#if VERBOSE
51#define VERBOSE_LOG(fmt, args...)  IOLog(fmt, ## args)
52#else
53#define VERBOSE_LOG(fmt, args...)
54#endif
55
56//------------------------------------------
57
58#define kClassName          "AppleFileSystemDriver"
59#define kMediaMatchKey      "media-match"
60#define kBootUUIDKey        "boot-uuid"
61#define kBootUUIDMediaKey   "boot-uuid-media"
62
63#define super IOService
64OSDefineMetaClassAndStructors(AppleFileSystemDriver, IOService)
65
66
67//------------------------------------------
68
69#if VERBOSE
70
71static OSString *
72createStringFromUUID(const uuid_t uu)
73{
74    char buf[64];
75
76    uuid_unparse_upper(uu, buf);
77
78    return OSString::withCString(buf);
79}
80
81#endif  // VERBOSE
82
83//------------------------------------------
84
85static IOReturn
86createUUIDFromName( const uuid_t uu_space, uint8_t *name_p, unsigned int name_len, uuid_t uu_result )
87{
88    /*
89     * Creates a UUID from a unique "name" in the given "name space".  See version 3 UUID.
90     */
91
92    MD5_CTX     md5c;
93
94    assert( sizeof( uuid_t ) == MD5_DIGEST_LENGTH );
95
96    memcpy( uu_result, uu_space, sizeof( uuid_t ) );
97
98    MD5Init( &md5c );
99    MD5Update( &md5c, uu_result, sizeof( uuid_t ) );
100    MD5Update( &md5c, name_p, name_len );
101    MD5Final( uu_result, &md5c );
102
103	// this UUID has been made version 3 style (i.e. via namespace)
104	// see "-uuid-urn-" IETF draft (which otherwise copies byte for byte)
105    uu_result[6] = 0x30 | ( uu_result[6] & 0x0F );
106    uu_result[8] = 0x80 | ( uu_result[8] & 0x3F );
107
108    return kIOReturnSuccess;
109}
110
111
112//------------------------------------------
113
114enum {
115    kHFSBlockSize = 512,
116    kVolumeUUIDValueLength = 8
117};
118
119typedef union VolumeUUID {
120    uint8_t bytes[kVolumeUUIDValueLength];
121    struct {
122        uint32_t high;
123        uint32_t low;
124    } v;
125} VolumeUUID;
126
127//------------------------------------------
128
129IOReturn
130AppleFileSystemDriver::readHFSUUID(IOMedia *media, void **uuidPtr)
131{
132    bool                       mediaIsOpen    = false;
133    UInt64                     mediaBlockSize = 0;
134    IOBufferMemoryDescriptor * buffer         = 0;
135    void *                     bytes          = 0;
136    UInt64                     bufferReadAt   = 0;
137    vm_size_t                  bufferSize     = 0;
138    IOReturn                   status         = kIOReturnError;
139    HFSMasterDirectoryBlock *  mdbPtr         = 0;
140    HFSPlusVolumeHeader *      volHdrPtr      = 0;
141    VolumeUUID *               volumeUUIDPtr  = (VolumeUUID *)uuidPtr;
142
143
144    DEBUG_LOG("%s::%s\n", kClassName, __func__);
145
146    do {
147
148        mediaBlockSize = media->getPreferredBlockSize();
149
150        bufferSize = IORound(sizeof(HFSMasterDirectoryBlock), mediaBlockSize);
151        buffer     = IOBufferMemoryDescriptor::withCapacity(bufferSize, kIODirectionIn);
152        if ( buffer == 0 ) break;
153
154        bytes = (void *) buffer->getBytesNoCopy();
155
156        mdbPtr = (HFSMasterDirectoryBlock *)bytes;
157        volHdrPtr = (HFSPlusVolumeHeader *)bytes;
158
159        // Open the media with read access.
160
161        mediaIsOpen = media->open(media, 0, kIOStorageAccessReader);
162        if ( mediaIsOpen == false ) break;
163
164        bufferReadAt = 2 * kHFSBlockSize;
165
166        status = media->read(media, bufferReadAt, buffer);
167        if ( status != kIOReturnSuccess )  break;
168
169        /*
170         * If this is a wrapped HFS Plus volume, read the Volume Header from
171         * sector 2 of the embedded volume.
172         */
173        if ( OSSwapBigToHostInt16(mdbPtr->drSigWord) == kHFSSigWord &&
174             OSSwapBigToHostInt16(mdbPtr->drEmbedSigWord) == kHFSPlusSigWord) {
175
176            u_int32_t   allocationBlockSize, firstAllocationBlock, startBlock, blockCount;
177
178            if (OSSwapBigToHostInt16(mdbPtr->drSigWord) != kHFSSigWord) {
179                break;
180            }
181
182            allocationBlockSize = OSSwapBigToHostInt32(mdbPtr->drAlBlkSiz);
183            firstAllocationBlock = OSSwapBigToHostInt16(mdbPtr->drAlBlSt);
184
185            if (OSSwapBigToHostInt16(mdbPtr->drEmbedSigWord) != kHFSPlusSigWord) {
186                break;
187            }
188
189            startBlock = OSSwapBigToHostInt16(mdbPtr->drEmbedExtent.startBlock);
190            blockCount = OSSwapBigToHostInt16(mdbPtr->drEmbedExtent.blockCount);
191
192            bufferReadAt = ((u_int64_t)startBlock * (u_int64_t)allocationBlockSize) +
193                ((u_int64_t)firstAllocationBlock * (u_int64_t)kHFSBlockSize) +
194                (u_int64_t)(2 * kHFSBlockSize);
195
196            status = media->read(media, bufferReadAt, buffer);
197            if ( status != kIOReturnSuccess )  break;
198        }
199
200        /*
201         * At this point, we have the MDB for plain HFS, or VHB for HFS Plus and HFSX
202         * volumes (including wrapped HFS Plus).  Verify the signature and grab the
203         * UUID from the Finder Info.
204         */
205        if (OSSwapBigToHostInt16(mdbPtr->drSigWord) == kHFSSigWord) {
206            bcopy((void *)&mdbPtr->drFndrInfo[6], volumeUUIDPtr->bytes, kVolumeUUIDValueLength);
207            status = kIOReturnSuccess;
208
209        } else if (OSSwapBigToHostInt16(volHdrPtr->signature) == kHFSPlusSigWord ||
210                   OSSwapBigToHostInt16(volHdrPtr->signature) == kHFSXSigWord) {
211            bcopy((void *)&volHdrPtr->finderInfo[24], volumeUUIDPtr->bytes, kVolumeUUIDValueLength);
212            status = kIOReturnSuccess;
213        } else {
214	    // status = 0 from earlier successful media->read()
215	    status = kIOReturnBadMedia;
216	}
217
218    } while (false);
219
220    if ( mediaIsOpen )  media->close(media);
221    if ( buffer )  buffer->release();
222
223    DEBUG_LOG("%s::%s finishes with status %d\n", kClassName, __func__, status);
224
225    return status;
226}
227
228//------------------------------------------
229
230bool
231AppleFileSystemDriver::mediaNotificationHandler(
232												void * target, void * ref,
233												IOService * service,
234												IONotifier * notifier)
235{
236    AppleFileSystemDriver *    fs;
237    IOMedia *                  media;
238    IOReturn                   status         = kIOReturnError;
239    OSString *                 contentHint;
240    const char *               contentStr;
241    VolumeUUID	               volumeUUID;
242    OSString *                 uuidProperty;
243    uuid_t                     uuid;
244    bool                       matched        = false;
245    bool                       isRAID         = false;
246
247    DEBUG_LOG("%s[%p]::%s -> '%s'\n", kClassName, target, __func__, service->getName());
248
249    do {
250        fs = OSDynamicCast( AppleFileSystemDriver, (IOService *)target );
251        if (fs == 0) break;
252
253        media = OSDynamicCast( IOMedia, service );
254        if (media == 0) break;
255
256        // i.e. does it know how big it is / have a block size
257        if ( media->isFormatted() == false )  break;
258
259	// the RAID might not be ready yet :P
260        isRAID = (media->getProperty(kAppleRAIDIsRAIDKey) == kOSBooleanTrue);
261	if (isRAID) {
262	    IOStorage *provider;
263	    OSString *status;
264
265	    if (!(provider = media->getProvider()))	goto notraid;
266	    if (!(status = OSDynamicCast(OSString,
267		    provider->getProperty(kAppleRAIDStatusKey))))  goto notraid;
268
269	    // if it decides to start working later, we'll get another shot
270	    if (!status->isEqualTo(kAppleRAIDStatusDegraded) &&
271			!status->isEqualTo(kAppleRAIDStatusOnline)) {
272		DEBUG_LOG("skipping prematurely-available RAID device");
273		break;
274	    }
275	}
276	notraid:
277
278        // If the media already has a UUID property, try that first.
279        uuidProperty = OSDynamicCast( OSString, media->getProperty("UUID") );
280        if (uuidProperty != NULL) {
281            if (fs->_uuidString && uuidProperty->isEqualTo(fs->_uuidString)) {
282		VERBOSE_LOG("existing UUID property matched\n");
283                matched = true;
284                break;
285            }
286        }
287
288	// only IOMedia's with content hints (perhaps empty) are interesting
289        contentHint = OSDynamicCast( OSString, media->getProperty(kIOMediaContentHintKey) );
290        if (contentHint == NULL)  break;
291        contentStr = contentHint->getCStringNoCopy();
292        if (contentStr == NULL)  break;
293
294        // probe based on content hint, but if the hint is
295        // empty and we see RAID, probe for anything we support
296        if ( strcmp(contentStr, "Apple_HFS" ) == 0 ||
297             strcmp(contentStr, "Apple_HFSX" ) == 0 ||
298             strcmp(contentStr, "Apple_Boot" ) == 0 ||
299             strcmp(contentStr, "Apple_Recovery" ) == 0 ||
300             strcmp(contentStr, "48465300-0000-11AA-AA11-00306543ECAC" ) == 0 ||  /* APPLE_HFS_UUID */
301             strcmp(contentStr, "426F6F74-0000-11AA-AA11-00306543ECAC" ) == 0 ||  /* APPLE_BOOT_UUID */
302             strcmp(contentStr, "5265636F-7665-11AA-AA11-00306543ECAC" ) == 0 ) { /* APPLE_RECOVERY_UUID */
303            status = readHFSUUID( media, (void **)&volumeUUID );
304        } else if (strlen(contentStr) == 0 && isRAID) {
305            // RAIDv1 has a content hint but is empty
306            status = readHFSUUID( media, (void **)&volumeUUID );
307        } else {
308            break;
309        }
310
311        if (status != kIOReturnSuccess) {
312            break;
313        }
314
315        if (createUUIDFromName( kFSUUIDNamespaceSHA1,
316                                volumeUUID.bytes, kVolumeUUIDValueLength,
317                                uuid ) != kIOReturnSuccess) {
318            break;
319        }
320
321#if VERBOSE
322        OSString *str = createStringFromUUID(uuid);
323	OSString *bsdn = OSDynamicCast(OSString,media->getProperty("BSD Name"));
324        if (str) {
325            IOLog("  UUID %s found on volume '%s' (%s)\n", str->getCStringNoCopy(),
326		    media->getName(), bsdn ? bsdn->getCStringNoCopy():"");
327            str->release();
328        }
329#endif
330
331        if (fs->_uuid) {
332            if ( uuid_compare(uuid, fs->_uuid) == 0 ) {
333                VERBOSE_LOG("  UUID matched on volume %s\n", media->getName());
334                matched = true;
335            }
336        }
337
338    } while (false);
339
340    if (matched) {
341
342        // prevent more notifications, if notifier is available
343        if (fs->_notifier != NULL) {
344            fs->_notifier->remove();
345            fs->_notifier = NULL;
346        }
347
348        DEBUG_LOG("%s::%s publishing boot-uuid-media '%s'\n", kClassName, __func__, media->getName());
349        IOService::publishResource( kBootUUIDMediaKey, media );
350
351        // Now that our job is done, get rid of the matching property
352        // and kill the driver.
353        fs->getResourceService()->removeProperty( kBootUUIDKey );
354        fs->terminate( kIOServiceRequired );
355
356        VERBOSE_LOG("%s[%p]::%s returning TRUE\n", kClassName, target, __func__);
357
358        return true;
359    }
360
361    DEBUG_LOG("%s[%p]::%s returning false\n", kClassName, target, __func__);
362#if DEBUG
363//IOSleep(5000);
364#endif
365    return false;
366}
367
368//------------------------------------------
369
370bool
371AppleFileSystemDriver::start(IOService * provider)
372{
373    OSDictionary *      matching;
374    OSString *          uuidString;
375    const char *        uuidCString;
376    IOService *         resourceService;
377    OSDictionary *      dict;
378
379
380    DEBUG_LOG("%s[%p]::%s\n", getName(), this, __func__);
381
382    DEBUG_LOG("%s provider is '%s'\n", getName(), provider->getName());
383
384    do {
385        resourceService = getResourceService();
386        if (resourceService == 0) break;
387
388        uuidString = OSDynamicCast( OSString, resourceService->getProperty("boot-uuid") );
389        if (uuidString) {
390            _uuidString = uuidString;
391            _uuidString->retain();
392            uuidCString = uuidString->getCStringNoCopy();
393            DEBUG_LOG("%s: got UUID string '%s'\n", getName(), uuidCString);
394            if (uuid_parse(uuidCString, _uuid) != 0) {
395                IOLog("%s: Invalid UUID string '%s'\n", getName(), uuidCString);
396                break;
397            }
398        } else {
399            IOLog("%s: Error getting boot-uuid property\n", getName());
400            break;
401        }
402
403        // Match IOMedia objects matching our criteria (from kext .plist)
404        dict = OSDynamicCast( OSDictionary, getProperty( kMediaMatchKey ) );
405        if (dict == 0) break;
406
407        dict = OSDictionary::withDictionary(dict);
408        if (dict == 0) break;
409
410        matching = IOService::serviceMatching( "IOMedia", dict );
411        if ( matching == 0 )
412            return false;
413
414        // Set up notification for newly-appearing devices.
415        // This will also notify us about existing devices.
416
417        _notifier = IOService::addMatchingNotification( gIOMatchedNotification, matching,
418                                                &mediaNotificationHandler,
419                                                this, 0 );
420        matching->release();
421
422        DEBUG_LOG("%s[%p]::%s finishes TRUE\n", getName(), this, __func__);
423
424        return true;
425
426    } while (false);
427
428    DEBUG_LOG("%s[%p]::%s finishes false\n", getName(), this, __func__);
429
430    return false;
431}
432
433//------------------------------------------
434
435void
436AppleFileSystemDriver::free()
437{
438    DEBUG_LOG("%s[%p]::%s\n", getName(), this, __func__);
439
440    if (_uuidString) _uuidString->release();
441    if (_notifier) _notifier->remove();
442
443    super::free();
444}
445
446//------------------------------------------
447
448
449