1/*
2 * Copyright (c) 1998-2014 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#include <IOKit/assert.h>
25#include <IOKit/IOBufferMemoryDescriptor.h>
26#include <IOKit/IOLib.h>
27#include <IOKit/storage/IOApplePartitionScheme.h>
28#include <libkern/OSByteOrder.h>
29
30#define super IOPartitionScheme
31OSDefineMetaClassAndStructors(IOApplePartitionScheme, IOPartitionScheme);
32
33//
34// Notes
35//
36// o the on-disk structure's fields are big-endian formatted
37// o the dpme_pblock_start and dpme_pblocks block values are:
38//   o for media without a driver map:
39//     o natural block size based
40//   o for media with a driver map:
41//     o driver map block size based, unless the driver map block size is 2048
42//       and a valid partition entry exists at a 512 byte offset into the disk,
43//       in which case, assume a 512 byte block size, except for the partition
44//       entries that lie on a 2048 byte multiple and are one of the following
45//       types: Apple_Patches, Apple_Driver, Apple_Driver43, Apple_Driver43_CD,
46//       Apple_Driver_ATA, Apple_Driver_ATAPI; in which case, we assume a 2048
47//       byte block size (for the one partition)
48// o the dpme_pblock_start block value is relative to the media container
49//
50
51bool IOApplePartitionScheme::init(OSDictionary * properties)
52{
53    //
54    // Initialize this object's minimal state.
55    //
56
57    // State our assumptions.
58
59    assert(sizeof(dpme)   == 512);                  // (compiler/platform check)
60    assert(sizeof(DDMap)  ==   8);                  // (compiler/platform check)
61    assert(sizeof(Block0) == 512);                  // (compiler/platform check)
62
63    // Ask our superclass' opinion.
64
65    if (super::init(properties) == false)  return false;
66
67    // Initialize our state.
68
69    _partitions = 0;
70
71    return true;
72}
73
74void IOApplePartitionScheme::free()
75{
76    //
77    // Free all of this object's outstanding resources.
78    //
79
80    if ( _partitions )  _partitions->release();
81
82    super::free();
83}
84
85IOService * IOApplePartitionScheme::probe(IOService * provider, SInt32 * score)
86{
87    //
88    // Determine whether the provider media contains an Apple partition map.
89    //
90
91    // State our assumptions.
92
93    assert(OSDynamicCast(IOMedia, provider));
94
95    // Ask superclass' opinion.
96
97    if (super::probe(provider, score) == 0)  return 0;
98
99    // Scan the provider media for an Apple partition map.
100
101    _partitions = scan(score);
102
103    return ( _partitions ) ? this : 0;
104}
105
106bool IOApplePartitionScheme::start(IOService * provider)
107{
108    //
109    // Publish the new media objects which represent our partitions.
110    //
111
112    IOMedia *    partition;
113    OSIterator * partitionIterator;
114
115    // State our assumptions.
116
117    assert(_partitions);
118
119    // Ask our superclass' opinion.
120
121    if ( super::start(provider) == false )  return false;
122
123    // Attach and register the new media objects representing our partitions.
124
125    partitionIterator = OSCollectionIterator::withCollection(_partitions);
126    if ( partitionIterator == 0 )  return false;
127
128    while ( (partition = (IOMedia *) partitionIterator->getNextObject()) )
129    {
130        if ( partition->attach(this) )
131        {
132            attachMediaObjectToDeviceTree(partition);
133
134            partition->registerService();
135        }
136    }
137
138    partitionIterator->release();
139
140    return true;
141}
142
143void IOApplePartitionScheme::stop(IOService * provider)
144{
145    //
146    // Clean up after the media objects we published before terminating.
147    //
148
149    IOMedia *    partition;
150    OSIterator * partitionIterator;
151
152    // State our assumptions.
153
154    assert(_partitions);
155
156    // Detach the media objects we previously attached to the device tree.
157
158    partitionIterator = OSCollectionIterator::withCollection(_partitions);
159
160    if ( partitionIterator )
161    {
162        while ( (partition = (IOMedia *) partitionIterator->getNextObject()) )
163        {
164            detachMediaObjectFromDeviceTree(partition);
165        }
166
167        partitionIterator->release();
168    }
169
170    super::stop(provider);
171}
172
173IOReturn IOApplePartitionScheme::requestProbe(IOOptionBits options)
174{
175    //
176    // Request that the provider media be re-scanned for partitions.
177    //
178
179    OSSet * partitions    = 0;
180    OSSet * partitionsNew;
181    SInt32  score         = 0;
182
183    // Scan the provider media for partitions.
184
185    partitionsNew = scan( &score );
186
187    if ( partitionsNew )
188    {
189        if ( lockForArbitration( false ) )
190        {
191            partitions = juxtaposeMediaObjects( _partitions, partitionsNew );
192
193            if ( partitions )
194            {
195                _partitions->release( );
196
197                _partitions = partitions;
198            }
199
200            unlockForArbitration( );
201        }
202
203        partitionsNew->release( );
204    }
205
206    return partitions ? kIOReturnSuccess : kIOReturnError;
207}
208
209OSSet * IOApplePartitionScheme::scan(SInt32 * score)
210{
211    //
212    // Scan the provider media for an Apple partition map.  Returns the set
213    // of media objects representing each of the partitions (the retain for
214    // the set is passed to the caller), or null should no partition map be
215    // found.  The default probe score can be adjusted up or down, based on
216    // the confidence of the scan.
217    //
218
219    IOBufferMemoryDescriptor * buffer         = 0;
220    UInt32                     bufferReadAt   = 0;
221    UInt32                     bufferSize     = 0;
222    UInt32                     dpmeBlockSize  = 0;
223    UInt32                     dpmeCount      = 0;
224    UInt32                     dpmeID         = 0;
225    dpme *                     dpmeMap        = 0;
226    UInt32                     dpmeMaxCount   = 0;
227    bool                       dpmeOldSchool  = false;
228    Block0 *                   driverMap      = 0;
229    IOMedia *                  media          = getProvider();
230    UInt64                     mediaBlockSize = media->getPreferredBlockSize();
231    bool                       mediaIsOpen    = false;
232    OSSet *                    partitions     = 0;
233    IOReturn                   status         = kIOReturnError;
234
235    // Determine whether this media is formatted.
236
237    if ( media->isFormatted() == false )  goto scanErr;
238
239    // Determine whether this media has an appropriate block size.
240
241    if ( (mediaBlockSize % sizeof(dpme)) )  goto scanErr;
242
243    // Allocate a buffer large enough to hold one map, rounded to a media block.
244
245    bufferSize = IORound(max(sizeof(Block0), sizeof(dpme)), mediaBlockSize);
246    buffer     = IOBufferMemoryDescriptor::withCapacity(
247                                           /* capacity      */ bufferSize,
248                                           /* withDirection */ kIODirectionIn );
249    if ( buffer == 0 )  goto scanErr;
250
251    // Allocate a set to hold the set of media objects representing partitions.
252
253    partitions = OSSet::withCapacity(8);
254    if ( partitions == 0 )  goto scanErr;
255
256    // Open the media with read access.
257
258    mediaIsOpen = open(this, 0, kIOStorageAccessReader);
259    if ( mediaIsOpen == false )  goto scanErr;
260
261    // Read the driver map into our buffer.
262
263    bufferReadAt = 0;
264
265    status = media->read(this, bufferReadAt, buffer);
266    if ( status != kIOReturnSuccess )  goto scanErr;
267
268    driverMap = (Block0 *) buffer->getBytesNoCopy();
269
270    // Determine the official block size to use to scan the partition entries.
271
272    dpmeBlockSize = mediaBlockSize;                      // (natural block size)
273
274    if ( OSSwapBigToHostInt16(driverMap->sbSig) == BLOCK0_SIGNATURE )
275    {
276        dpmeBlockSize = OSSwapBigToHostInt16(driverMap->sbBlkSize);
277
278        // Increase the probe score when a driver map is detected, since we are
279        // more confident in the match when it is present.  This will eliminate
280        // conflicts with FDisk when it shares the same block as the driver map.
281
282        *score += 2000;
283    }
284
285    // Determine whether we have an old school partition map, where there is
286    // a partition entry at a 512 byte offset into the disk, even though the
287    // driver map block size is 2048.
288
289    if ( dpmeBlockSize == 2048 )
290    {
291        if ( bufferSize >= sizeof(Block0) + sizeof(dpme) )       // (in buffer?)
292        {
293            dpmeMap = (dpme *) (driverMap + 1);
294        }
295        else                                                  // (not in buffer)
296        {
297            // Read the partition entry at byte offset 512 into our buffer.
298
299            bufferReadAt = sizeof(dpme);
300
301            status = media->read(this, bufferReadAt, buffer);
302            if ( status != kIOReturnSuccess )  goto scanErr;
303
304            dpmeMap = (dpme *) buffer->getBytesNoCopy();
305        }
306
307        // Determine whether the partition entry signature is present.
308
309        if ( OSSwapBigToHostInt16(dpmeMap->dpme_signature) == DPME_SIGNATURE )
310        {
311            dpmeBlockSize = sizeof(dpme);             // (old school block size)
312            dpmeOldSchool = true;
313        }
314    }
315
316    // Scan the media for Apple partition entries.
317
318    for ( dpmeID = 1, dpmeCount = 1; dpmeID <= dpmeCount; dpmeID++ )
319    {
320        UInt32 partitionBlockSize = dpmeBlockSize;
321
322        // Determine whether we've exhausted the current buffer of entries.
323
324        if ( dpmeID * dpmeBlockSize + sizeof(dpme) > bufferReadAt + bufferSize )
325        {
326            // Read the next partition entry into our buffer.
327
328            bufferReadAt = dpmeID * dpmeBlockSize;
329
330            status = media->read(this, bufferReadAt, buffer);
331            if ( status != kIOReturnSuccess )  goto scanErr;
332        }
333
334        dpmeMap = (dpme *) ( ((UInt8 *) buffer->getBytesNoCopy()) +
335                             (dpmeID * dpmeBlockSize) - bufferReadAt );
336
337        // Determine whether the partition entry signature is present.
338
339        if ( OSSwapBigToHostInt16(dpmeMap->dpme_signature) != DPME_SIGNATURE )
340        {
341            goto scanErr;
342        }
343
344        // Obtain an accurate number of entries in the partition map.
345
346        if ( !strncmp(dpmeMap->dpme_type, "Apple_partition_map", sizeof(dpmeMap->dpme_type)) ||
347             !strncmp(dpmeMap->dpme_type, "Apple_Partition_Map", sizeof(dpmeMap->dpme_type)) ||
348             !strncmp(dpmeMap->dpme_type, "Apple_patition_map",  sizeof(dpmeMap->dpme_type)) )
349        {
350            dpmeCount    = OSSwapBigToHostInt32(dpmeMap->dpme_map_entries);
351            dpmeMaxCount = OSSwapBigToHostInt32(dpmeMap->dpme_pblocks);
352        }
353        else if ( dpmeCount == 1 )
354        {
355            dpmeCount = OSSwapBigToHostInt32(dpmeMap->dpme_map_entries);
356        }
357
358        // Obtain an accurate block size for an old school partition map.
359
360        if ( dpmeOldSchool && (dpmeID % 4) == 0 )
361        {
362            if ( !strncmp(dpmeMap->dpme_type, "Apple_Driver",       sizeof(dpmeMap->dpme_type)) ||
363                 !strncmp(dpmeMap->dpme_type, "Apple_Driver43",     sizeof(dpmeMap->dpme_type)) ||
364                 !strncmp(dpmeMap->dpme_type, "Apple_Driver43_CD",  sizeof(dpmeMap->dpme_type)) ||
365                 !strncmp(dpmeMap->dpme_type, "Apple_Driver_ATA",   sizeof(dpmeMap->dpme_type)) ||
366                 !strncmp(dpmeMap->dpme_type, "Apple_Driver_ATAPI", sizeof(dpmeMap->dpme_type)) ||
367                 !strncmp(dpmeMap->dpme_type, "Apple_Patches",      sizeof(dpmeMap->dpme_type)) )
368            {
369                partitionBlockSize = 2048;
370            }
371        }
372
373        // Determine whether the partition is corrupt (fatal).
374
375        if ( isPartitionCorrupt(
376                                 /* partition          */ dpmeMap,
377                                 /* partitionID        */ dpmeID,
378                                 /* partitionBlockSize */ partitionBlockSize ) )
379        {
380            goto scanErr;
381        }
382
383        // Determine whether the partition is invalid (skipped).
384
385        if ( isPartitionInvalid(
386                                 /* partition          */ dpmeMap,
387                                 /* partitionID        */ dpmeID,
388                                 /* partitionBlockSize */ partitionBlockSize ) )
389        {
390            continue;
391        }
392
393        // Create a media object to represent this partition.
394
395        IOMedia * newMedia = instantiateMediaObject(
396                                 /* partition          */ dpmeMap,
397                                 /* partitionID        */ dpmeID,
398                                 /* partitionBlockSize */ partitionBlockSize );
399
400        if ( newMedia )
401        {
402            partitions->setObject(newMedia);
403            newMedia->release();
404        }
405    }
406
407    // Determine whether we ever came accross an Apple_partition_map partition.
408
409    if ( dpmeMaxCount == 0 )  goto scanErr;
410
411    // Release our resources.
412
413    close(this);
414    buffer->release();
415
416    return partitions;
417
418scanErr:
419
420    // Release our resources.
421
422    if ( mediaIsOpen )  close(this);
423    if ( partitions )  partitions->release();
424    if ( buffer )  buffer->release();
425
426    return 0;
427}
428
429bool IOApplePartitionScheme::isPartitionCorrupt( dpme * partition,
430                                                 UInt32 partitionID,
431                                                 UInt32 partitionBlockSize )
432{
433    //
434    // Ask whether the given partition appears to be corrupt.  A partition that
435    // is corrupt will cause the failure of the Apple partition map recognition
436    // altogether.
437    //
438
439    if ( !strncmp(partition->dpme_type, "CD_ROM_Mode_1", sizeof(partition->dpme_type)) )  return true;
440
441    return false;
442}
443
444bool IOApplePartitionScheme::isPartitionInvalid( dpme * partition,
445                                                 UInt32 partitionID,
446                                                 UInt32 partitionBlockSize )
447{
448    //
449    // Ask whether the given partition appears to be invalid.  A partition that
450    // is invalid will cause it to be skipped in the scan, but will not cause a
451    // failure of the Apple partition map recognition.
452    //
453
454    IOMedia * media         = getProvider();
455    UInt64    partitionBase = 0;
456    UInt64    partitionSize = 0;
457
458    // Compute the relative byte position and size of the new partition.
459
460    partitionBase  = OSSwapBigToHostInt32(partition->dpme_pblock_start);
461    partitionSize  = OSSwapBigToHostInt32(partition->dpme_pblocks);
462    partitionBase *= partitionBlockSize;
463    partitionSize *= partitionBlockSize;
464
465    // Determine whether the partition is a placeholder.
466
467    if ( partitionSize == 0 )  return true;
468
469    // Determine whether the partition starts at (or past) the end-of-media.
470
471    if ( partitionBase >= media->getSize() )  return true;
472
473    return false;
474}
475
476IOMedia * IOApplePartitionScheme::instantiateMediaObject(
477                                                     dpme * partition,
478                                                     UInt32 partitionID,
479                                                     UInt32 partitionBlockSize )
480{
481    //
482    // Instantiate a new media object to represent the given partition.
483    //
484
485    IOMedia * media               = getProvider();
486    UInt64    mediaBlockSize      = media->getPreferredBlockSize();
487    UInt64    partitionBase       = 0;
488    char      partitionHint[DPISTRLEN + 1];
489    bool      partitionIsWritable = media->isWritable();
490    char      partitionName[DPISTRLEN + 1];
491    UInt64    partitionSize       = 0;
492
493    strncpy(partitionHint, partition->dpme_type, DPISTRLEN);
494    strncpy(partitionName, partition->dpme_name, DPISTRLEN);
495
496    partitionHint[DPISTRLEN] = 0;
497    partitionName[DPISTRLEN] = 0;
498
499    // Compute the relative byte position and size of the new partition.
500
501    partitionBase  = OSSwapBigToHostInt32(partition->dpme_pblock_start);
502    partitionSize  = OSSwapBigToHostInt32(partition->dpme_pblocks);
503    partitionBase *= partitionBlockSize;
504    partitionSize *= partitionBlockSize;
505
506    // Clip the size of the new partition if it extends past the end-of-media.
507
508    if ( partitionBase + partitionSize > media->getSize() )
509    {
510        partitionSize = media->getSize() - partitionBase;
511    }
512
513    // Determine whether the new partition type is Apple_Free, which we choose
514    // not to publish because it is an internal concept to the partition map.
515
516    if ( !strncmp(partition->dpme_type, "Apple_Free", sizeof(partition->dpme_type)) )  return 0;
517
518    // Determine whether the new partition is read-only.
519    //
520    // Note that we treat the misspelt Apple_patition_map entries as equivalent
521    // to Apple_partition_map entries due to the messed up CDs noted in 2513960.
522
523    if ( !strncmp(partition->dpme_type, "Apple_partition_map", sizeof(partition->dpme_type)) ||
524         !strncmp(partition->dpme_type, "Apple_Partition_Map", sizeof(partition->dpme_type)) ||
525         !strncmp(partition->dpme_type, "Apple_patition_map",  sizeof(partition->dpme_type)) ||
526         ( OSSwapBigToHostInt32(partition->dpme_flags) &
527           ( DPME_FLAGS_WRITABLE | DPME_FLAGS_VALID )  ) == DPME_FLAGS_VALID )
528    {
529        partitionIsWritable = false;
530    }
531
532    // Create the new media object.
533
534    IOMedia * newMedia = instantiateDesiredMediaObject(
535                                 /* partition          */ partition,
536                                 /* partitionID        */ partitionID,
537                                 /* partitionBlockSize */ partitionBlockSize );
538
539    if ( newMedia )
540    {
541        if ( newMedia->init(
542                /* base               */ partitionBase,
543                /* size               */ partitionSize,
544                /* preferredBlockSize */ mediaBlockSize,
545                /* attributes         */ media->getAttributes(),
546                /* isWhole            */ false,
547                /* isWritable         */ partitionIsWritable,
548                /* contentHint        */ partitionHint ) )
549        {
550            // Set a name for this partition.
551
552            char name[24];
553            snprintf(name, sizeof(name), "Untitled %d", (int) partitionID);
554            newMedia->setName(partitionName[0] ? partitionName : name);
555
556            // Set a location value (the partition number) for this partition.
557
558            char location[12];
559            snprintf(location, sizeof(location), "%d", (int) partitionID);
560            newMedia->setLocation(location);
561
562            // Set the "Base" key for this partition.
563
564            newMedia->setProperty(kIOMediaBaseKey, partitionBase, 64);
565
566            // Set the "Partition ID" key for this partition.
567
568            newMedia->setProperty(kIOMediaPartitionIDKey, partitionID, 32);
569        }
570        else
571        {
572            newMedia->release();
573            newMedia = 0;
574        }
575    }
576
577    return newMedia;
578}
579
580IOMedia * IOApplePartitionScheme::instantiateDesiredMediaObject(
581                                                     dpme * partition,
582                                                     UInt32 partitionID,
583                                                     UInt32 partitionBlockSize )
584{
585    //
586    // Allocate a new media object (called from instantiateMediaObject).
587    //
588
589    return new IOMedia;
590}
591
592#ifndef __LP64__
593bool IOApplePartitionScheme::attachMediaObjectToDeviceTree(IOMedia * media)
594{
595    //
596    // Attach the given media object to the device tree plane.
597    //
598
599    return super::attachMediaObjectToDeviceTree(media);
600}
601
602void IOApplePartitionScheme::detachMediaObjectFromDeviceTree(IOMedia * media)
603{
604    //
605    // Detach the given media object from the device tree plane.
606    //
607
608    super::detachMediaObjectFromDeviceTree(media);
609}
610#endif /* !__LP64__ */
611
612OSMetaClassDefineReservedUnused(IOApplePartitionScheme,  0);
613OSMetaClassDefineReservedUnused(IOApplePartitionScheme,  1);
614OSMetaClassDefineReservedUnused(IOApplePartitionScheme,  2);
615OSMetaClassDefineReservedUnused(IOApplePartitionScheme,  3);
616OSMetaClassDefineReservedUnused(IOApplePartitionScheme,  4);
617OSMetaClassDefineReservedUnused(IOApplePartitionScheme,  5);
618OSMetaClassDefineReservedUnused(IOApplePartitionScheme,  6);
619OSMetaClassDefineReservedUnused(IOApplePartitionScheme,  7);
620OSMetaClassDefineReservedUnused(IOApplePartitionScheme,  8);
621OSMetaClassDefineReservedUnused(IOApplePartitionScheme,  9);
622OSMetaClassDefineReservedUnused(IOApplePartitionScheme, 10);
623OSMetaClassDefineReservedUnused(IOApplePartitionScheme, 11);
624OSMetaClassDefineReservedUnused(IOApplePartitionScheme, 12);
625OSMetaClassDefineReservedUnused(IOApplePartitionScheme, 13);
626OSMetaClassDefineReservedUnused(IOApplePartitionScheme, 14);
627OSMetaClassDefineReservedUnused(IOApplePartitionScheme, 15);
628