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/IOFDiskPartitionScheme.h>
28#include <libkern/OSByteOrder.h>
29
30#define super IOPartitionScheme
31OSDefineMetaClassAndStructors(IOFDiskPartitionScheme, IOPartitionScheme);
32
33//
34// Notes
35//
36// o the on-disk structure's fields are little-endian formatted
37// o the relsect and numsect block values assume the drive's natural block size
38// o the relsect block value is:
39//   o for data partitions:
40//     o relative to the FDisk map that defines the partition
41//   o for extended partitions defined in the root-level FDisk map:
42//     o relative to the FDisk map that defines the partition (start of disk)
43//   o for extended partitions defined in a second-level or deeper FDisk map:
44//     o relative to the second-level FDisk map, regardless of depth
45// o the valid extended partition types are: 0x05, 0x0F, 0x85
46// o there should be no more than one extended partition defined per FDisk map
47//
48
49#define kIOFDiskPartitionSchemeContentTable "Content Table"
50
51bool IOFDiskPartitionScheme::init(OSDictionary * properties)
52{
53    //
54    // Initialize this object's minimal state.
55    //
56
57    // State our assumptions.
58
59    assert(sizeof(fdisk_part) ==  16);              // (compiler/platform check)
60    assert(sizeof(disk_blk0)  == 512);              // (compiler/platform check)
61
62    // Ask our superclass' opinion.
63
64    if ( super::init(properties) == false )  return false;
65
66    // Initialize our state.
67
68    _partitions = 0;
69
70    return true;
71}
72
73void IOFDiskPartitionScheme::free()
74{
75    //
76    // Free all of this object's outstanding resources.
77    //
78
79    if ( _partitions )  _partitions->release();
80
81    super::free();
82}
83
84IOService * IOFDiskPartitionScheme::probe(IOService * provider, SInt32 * score)
85{
86    //
87    // Determine whether the provider media contains an FDisk partition map.
88    //
89
90    // State our assumptions.
91
92    assert(OSDynamicCast(IOMedia, provider));
93
94    // Ask our superclass' opinion.
95
96    if ( super::probe(provider, score) == 0 )  return 0;
97
98    // Scan the provider media for an FDisk partition map.
99
100    _partitions = scan(score);
101
102    // There might be an FDisk partition scheme on disk with boot code, but with
103    // no partitions defined.  We don't consider this a match and return failure
104    // from probe.
105
106    if ( _partitions && _partitions->getCount() == 0 )
107    {
108        _partitions->release();
109        _partitions = 0;
110    }
111
112    return ( _partitions ) ? this : 0;
113}
114
115bool IOFDiskPartitionScheme::start(IOService * provider)
116{
117    //
118    // Publish the new media objects which represent our partitions.
119    //
120
121    IOMedia *    partition;
122    OSIterator * partitionIterator;
123
124    // State our assumptions.
125
126    assert(_partitions);
127
128    // Ask our superclass' opinion.
129
130    if ( super::start(provider) == false )  return false;
131
132    // Attach and register the new media objects representing our partitions.
133
134    partitionIterator = OSCollectionIterator::withCollection(_partitions);
135    if ( partitionIterator == 0 )  return false;
136
137    while ( (partition = (IOMedia *) partitionIterator->getNextObject()) )
138    {
139        if ( partition->attach(this) )
140        {
141            attachMediaObjectToDeviceTree(partition);
142
143            partition->registerService();
144        }
145    }
146
147    partitionIterator->release();
148
149    return true;
150}
151
152void IOFDiskPartitionScheme::stop(IOService * provider)
153{
154    //
155    // Clean up after the media objects we published before terminating.
156    //
157
158    IOMedia *    partition;
159    OSIterator * partitionIterator;
160
161    // State our assumptions.
162
163    assert(_partitions);
164
165    // Detach the media objects we previously attached to the device tree.
166
167    partitionIterator = OSCollectionIterator::withCollection(_partitions);
168
169    if ( partitionIterator )
170    {
171        while ( (partition = (IOMedia *) partitionIterator->getNextObject()) )
172        {
173            detachMediaObjectFromDeviceTree(partition);
174        }
175
176        partitionIterator->release();
177    }
178
179    super::stop(provider);
180}
181
182IOReturn IOFDiskPartitionScheme::requestProbe(IOOptionBits options)
183{
184    //
185    // Request that the provider media be re-scanned for partitions.
186    //
187
188    OSSet * partitions    = 0;
189    OSSet * partitionsNew;
190    SInt32  score         = 0;
191
192    // Scan the provider media for partitions.
193
194    partitionsNew = scan( &score );
195
196    if ( partitionsNew )
197    {
198        if ( lockForArbitration( false ) )
199        {
200            partitions = juxtaposeMediaObjects( _partitions, partitionsNew );
201
202            if ( partitions )
203            {
204                _partitions->release( );
205
206                _partitions = partitions;
207            }
208
209            unlockForArbitration( );
210        }
211
212        partitionsNew->release( );
213    }
214
215    return partitions ? kIOReturnSuccess : kIOReturnError;
216}
217
218OSSet * IOFDiskPartitionScheme::scan(SInt32 * score)
219{
220    //
221    // Scan the provider media for an FDisk partition map.  Returns the set
222    // of media objects representing each of the partitions (the retain for
223    // the set is passed to the caller), or null should no partition map be
224    // found.  The default probe score can be adjusted up or down, based on
225    // the confidence of the scan.
226    //
227
228    IOBufferMemoryDescriptor * buffer         = 0;
229    UInt32                     bufferSize     = 0;
230    UInt32                     fdiskBlock     = 0;
231    UInt32                     fdiskBlockExtn = 0;
232    UInt32                     fdiskBlockNext = 0;
233    UInt32                     fdiskID        = 0;
234    disk_blk0 *                fdiskMap       = 0;
235    IOMedia *                  media          = getProvider();
236    UInt64                     mediaBlockSize = media->getPreferredBlockSize();
237    bool                       mediaIsOpen    = false;
238    OSSet *                    partitions     = 0;
239    IOReturn                   status         = kIOReturnError;
240
241    // Determine whether this media is formatted.
242
243    if ( media->isFormatted() == false )  goto scanErr;
244
245    // Determine whether this media has an appropriate block size.
246
247    if ( (mediaBlockSize % sizeof(disk_blk0)) )  goto scanErr;
248
249    // Allocate a buffer large enough to hold one map, rounded to a media block.
250
251    bufferSize = IORound(sizeof(disk_blk0), mediaBlockSize);
252    buffer     = IOBufferMemoryDescriptor::withCapacity(
253                                           /* capacity      */ bufferSize,
254                                           /* withDirection */ kIODirectionIn );
255    if ( buffer == 0 )  goto scanErr;
256
257    // Allocate a set to hold the set of media objects representing partitions.
258
259    partitions = OSSet::withCapacity(4);
260    if ( partitions == 0 )  goto scanErr;
261
262    // Open the media with read access.
263
264    mediaIsOpen = open(this, 0, kIOStorageAccessReader);
265    if ( mediaIsOpen == false )  goto scanErr;
266
267    // Scan the media for FDisk partition map(s).
268
269    do
270    {
271        // Read the next FDisk map into our buffer.
272
273        status = media->read(this, fdiskBlock * mediaBlockSize, buffer);
274        if ( status != kIOReturnSuccess )  goto scanErr;
275
276        fdiskMap = (disk_blk0 *) buffer->getBytesNoCopy();
277
278        // Determine whether the partition map signature is present.
279
280        if ( OSSwapLittleToHostInt16(fdiskMap->signature) != DISK_SIGNATURE )
281        {
282            goto scanErr;
283        }
284
285        // Scan for valid partition entries in the partition map.
286
287        fdiskBlockNext = 0;
288
289        for ( unsigned index = 0; index < DISK_NPART; index++ )
290        {
291            // Determine whether this is an extended (vs. data) partition.
292
293            if ( isPartitionExtended(fdiskMap->parts + index) )    // (extended)
294            {
295                // If peer extended partitions exist, we accept only the first.
296
297                if ( fdiskBlockNext == 0 )      // (no peer extended partition)
298                {
299                    fdiskBlockNext = fdiskBlockExtn +
300                                     OSSwapLittleToHostInt32(
301                                    /* data */ fdiskMap->parts[index].relsect );
302
303                    if ( fdiskBlockNext * mediaBlockSize >= media->getSize() )
304                    {
305                        fdiskBlockNext = 0;       // (exceeds confines of media)
306                    }
307                }
308            }
309            else if ( isPartitionUsed(fdiskMap->parts + index) )       // (data)
310            {
311                // Prepare this partition's ID.
312
313                fdiskID = ( fdiskBlock == 0 ) ? (index + 1) : (fdiskID + 1);
314
315                // Determine whether the partition is corrupt (fatal).
316
317                if ( isPartitionCorrupt(
318                                   /* partition   */ fdiskMap->parts + index,
319                                   /* partitionID */ fdiskID,
320                                   /* fdiskBlock  */ fdiskBlock ) )
321                {
322                    goto scanErr;
323                }
324
325                // Determine whether the partition is invalid (skipped).
326
327                if ( isPartitionInvalid(
328                                   /* partition   */ fdiskMap->parts + index,
329                                   /* partitionID */ fdiskID,
330                                   /* fdiskBlock  */ fdiskBlock ) )
331                {
332                    continue;
333                }
334
335                // Create a media object to represent this partition.
336
337                IOMedia * newMedia = instantiateMediaObject(
338                                   /* partition   */ fdiskMap->parts + index,
339                                   /* partitionID */ fdiskID,
340                                   /* fdiskBlock  */ fdiskBlock );
341
342                if ( newMedia )
343                {
344                    partitions->setObject(newMedia);
345                    newMedia->release();
346                }
347            }
348        }
349
350        // Prepare for first extended partition, if any.
351
352        if ( fdiskBlock == 0 )
353        {
354            fdiskID        = DISK_NPART;
355            fdiskBlockExtn = fdiskBlockNext;
356        }
357
358    } while ( (fdiskBlock = fdiskBlockNext) );
359
360    // Release our resources.
361
362    close(this);
363    buffer->release();
364
365    return partitions;
366
367scanErr:
368
369    // Release our resources.
370
371    if ( mediaIsOpen )  close(this);
372    if ( partitions )  partitions->release();
373    if ( buffer )  buffer->release();
374
375    return 0;
376}
377
378bool IOFDiskPartitionScheme::isPartitionExtended(fdisk_part * partition)
379{
380    //
381    // Ask whether the given partition is extended.
382    //
383
384    return ( partition->systid == 0x05 ||
385             partition->systid == 0x0F ||
386             partition->systid == 0x85 );
387}
388
389bool IOFDiskPartitionScheme::isPartitionUsed(fdisk_part * partition)
390{
391    //
392    // Ask whether the given partition is used.
393    //
394
395    return ( partition->systid != 0 && partition->numsect != 0 );
396}
397
398bool IOFDiskPartitionScheme::isPartitionCorrupt( fdisk_part * partition,
399                                                 UInt32       partitionID,
400                                                 UInt32       fdiskBlock )
401{
402    //
403    // Ask whether the given partition appears to be corrupt.  A partition that
404    // is corrupt will cause the failure of the FDisk partition map recognition
405    // altogether.
406    //
407
408    // Determine whether the boot indicator is valid.
409
410    if ( (partition->bootid & 0x7F) )  return true;
411
412    return false;
413}
414
415bool IOFDiskPartitionScheme::isPartitionInvalid( fdisk_part * partition,
416                                                 UInt32       partitionID,
417                                                 UInt32       fdiskBlock )
418{
419    //
420    // Ask whether the given partition appears to be invalid.  A partition that
421    // is invalid will cause it to be skipped in the scan, but will not cause a
422    // failure of the FDisk partition map recognition.
423    //
424
425    IOMedia * media          = getProvider();
426    UInt64    mediaBlockSize = media->getPreferredBlockSize();
427    UInt64    partitionBase  = 0;
428    UInt64    partitionSize  = 0;
429
430    // Compute the relative byte position and size of the new partition.
431
432    partitionBase  = OSSwapLittleToHostInt32(partition->relsect) + fdiskBlock;
433    partitionSize  = OSSwapLittleToHostInt32(partition->numsect);
434    partitionBase *= mediaBlockSize;
435    partitionSize *= mediaBlockSize;
436
437    // Determine whether the partition shares space with the partition map.
438
439    if ( partitionBase == fdiskBlock * mediaBlockSize )  return true;
440
441    // Determine whether the partition starts at (or past) the end-of-media.
442
443    if ( partitionBase >= media->getSize() )  return true;
444
445    return false;
446}
447
448IOMedia * IOFDiskPartitionScheme::instantiateMediaObject(
449                                                     fdisk_part * partition,
450                                                     UInt32       partitionID,
451                                                     UInt32       fdiskBlock )
452{
453    //
454    // Instantiate a new media object to represent the given partition.
455    //
456
457    IOMedia * media          = getProvider();
458    UInt64    mediaBlockSize = media->getPreferredBlockSize();
459    UInt64    partitionBase  = 0;
460    char *    partitionHint  = 0;
461    UInt64    partitionSize  = 0;
462
463    // Compute the relative byte position and size of the new partition.
464
465    partitionBase  = OSSwapLittleToHostInt32(partition->relsect) + fdiskBlock;
466    partitionSize  = OSSwapLittleToHostInt32(partition->numsect);
467    partitionBase *= mediaBlockSize;
468    partitionSize *= mediaBlockSize;
469
470    // Clip the size of the new partition if it extends past the end-of-media.
471
472    if ( partitionBase + partitionSize > media->getSize() )
473    {
474        partitionSize = media->getSize() - partitionBase;
475    }
476
477    // Look up a type for the new partition.
478
479    char hintIndex[5];
480
481    snprintf(hintIndex, sizeof(hintIndex), "0x%02X", partition->systid & 0xFF);
482
483    partitionHint = hintIndex;
484
485    OSDictionary * hintTable = OSDynamicCast(
486              /* type     */ OSDictionary,
487              /* instance */ getProperty(kIOFDiskPartitionSchemeContentTable) );
488
489    if ( hintTable )
490    {
491        OSString * hintValue;
492
493        hintValue = OSDynamicCast(OSString, hintTable->getObject(hintIndex));
494
495        if ( hintValue ) partitionHint = (char *) hintValue->getCStringNoCopy();
496    }
497
498    // Create the new media object.
499
500    IOMedia * newMedia = instantiateDesiredMediaObject(
501                                   /* partition   */ partition,
502                                   /* partitionID */ partitionID,
503                                   /* fdiskBlock  */ fdiskBlock );
504
505    if ( newMedia )
506    {
507         if ( newMedia->init(
508                /* base               */ partitionBase,
509                /* size               */ partitionSize,
510                /* preferredBlockSize */ mediaBlockSize,
511                /* attributes         */ media->getAttributes(),
512                /* isWhole            */ false,
513                /* isWritable         */ media->isWritable(),
514                /* contentHint        */ partitionHint ) )
515        {
516            // Set a name for this partition.
517
518            char name[24];
519            snprintf(name, sizeof(name), "Untitled %d", (int) partitionID);
520            newMedia->setName(name);
521
522            // Set a location value (the partition number) for this partition.
523
524            char location[12];
525            snprintf(location, sizeof(location), "%d", (int) partitionID);
526            newMedia->setLocation(location);
527
528            // Set the "Base" key for this partition.
529
530            newMedia->setProperty(kIOMediaBaseKey, partitionBase, 64);
531
532            // Set the "Partition ID" key for this partition.
533
534            newMedia->setProperty(kIOMediaPartitionIDKey, partitionID, 32);
535        }
536        else
537        {
538            newMedia->release();
539            newMedia = 0;
540        }
541    }
542
543    return newMedia;
544}
545
546IOMedia * IOFDiskPartitionScheme::instantiateDesiredMediaObject(
547                                                     fdisk_part * partition,
548                                                     UInt32       partitionID,
549                                                     UInt32       fdiskBlock )
550{
551    //
552    // Allocate a new media object (called from instantiateMediaObject).
553    //
554
555    return new IOMedia;
556}
557
558#ifndef __LP64__
559bool IOFDiskPartitionScheme::attachMediaObjectToDeviceTree(IOMedia * media)
560{
561    //
562    // Attach the given media object to the device tree plane.
563    //
564
565    return super::attachMediaObjectToDeviceTree(media);
566}
567
568void IOFDiskPartitionScheme::detachMediaObjectFromDeviceTree(IOMedia * media)
569{
570    //
571    // Detach the given media object from the device tree plane.
572    //
573
574    super::detachMediaObjectFromDeviceTree(media);
575}
576#endif /* !__LP64__ */
577
578#ifdef __LP64__
579OSMetaClassDefineReservedUnused(IOFDiskPartitionScheme,  0);
580OSMetaClassDefineReservedUnused(IOFDiskPartitionScheme,  1);
581#else /* !__LP64__ */
582OSMetaClassDefineReservedUsed(IOFDiskPartitionScheme,  0);
583OSMetaClassDefineReservedUsed(IOFDiskPartitionScheme,  1);
584#endif /* !__LP64__ */
585OSMetaClassDefineReservedUnused(IOFDiskPartitionScheme,  2);
586OSMetaClassDefineReservedUnused(IOFDiskPartitionScheme,  3);
587OSMetaClassDefineReservedUnused(IOFDiskPartitionScheme,  4);
588OSMetaClassDefineReservedUnused(IOFDiskPartitionScheme,  5);
589OSMetaClassDefineReservedUnused(IOFDiskPartitionScheme,  6);
590OSMetaClassDefineReservedUnused(IOFDiskPartitionScheme,  7);
591OSMetaClassDefineReservedUnused(IOFDiskPartitionScheme,  8);
592OSMetaClassDefineReservedUnused(IOFDiskPartitionScheme,  9);
593OSMetaClassDefineReservedUnused(IOFDiskPartitionScheme, 10);
594OSMetaClassDefineReservedUnused(IOFDiskPartitionScheme, 11);
595OSMetaClassDefineReservedUnused(IOFDiskPartitionScheme, 12);
596OSMetaClassDefineReservedUnused(IOFDiskPartitionScheme, 13);
597OSMetaClassDefineReservedUnused(IOFDiskPartitionScheme, 14);
598OSMetaClassDefineReservedUnused(IOFDiskPartitionScheme, 15);
599