|
|
1.1 root 1: /*
2: * Copyright (c) 1998-2000 Apple Computer, Inc. All rights reserved.
3: *
4: * @APPLE_LICENSE_HEADER_START@
5: *
6: * The contents of this file constitute Original Code as defined in and
7: * are subject to the Apple Public Source License Version 1.1 (the
8: * "License"). You may not use this file except in compliance with the
9: * License. Please obtain a copy of the License at
10: * http://www.apple.com/publicsource and read it before using this file.
11: *
12: * This Original Code and all software distributed under the License are
13: * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER
14: * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
15: * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
16: * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the
17: * License for the specific language governing rights and limitations
18: * under the License.
19: *
20: * @APPLE_LICENSE_HEADER_END@
21: */
22:
23: #include <machine/limits.h> // (ULONG_MAX, ...)
24: #include <IOKit/IODeviceTreeSupport.h> // (gIODTPlane, ...)
25: #include <IOKit/storage/IOMedia.h>
26:
27: #define super IOStorage
28: OSDefineMetaClassAndStructors(IOMedia, IOStorage)
29:
30: // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
31:
32: bool IOMedia::init(UInt64 base,
33: UInt64 size,
34: UInt64 preferredBlockSize,
35: bool isEjectable,
36: bool isWhole,
37: bool isWritable,
38: const char * contentHint = 0,
39: OSDictionary * properties = 0)
40: {
41: //
42: // Initialize this object's minimal state.
43: //
44:
45: if (super::init(properties) == false) return false;
46:
47: _mediaBase = base;
48: _mediaSize = size;
49: _isEjectable = isEjectable;
50: _isWhole = isWhole;
51: _isWritable = isWritable;
52: _openLevel = kAccessNone;
53: _openReaders = OSSet::withCapacity(1);
54: _openReaderWriter = 0;
55: _preferredBlockSize = preferredBlockSize;
56:
57: if (_openReaders == 0) return false;
58:
59: //
60: // Create the standard media registry properties.
61: //
62:
63: setProperty(kIOMediaContent, contentHint ? contentHint : "");
64: setProperty(kIOMediaContentHint, contentHint ? contentHint : "");
65: setProperty(kIOMediaEjectable, isEjectable);
66: setProperty(kIOMediaLeaf, true);
67: setProperty(kIOMediaSize, size, 64);
68: setProperty(kIOMediaWhole, isWhole);
69: setProperty(kIOMediaWritable, isWritable);
70:
71: return true;
72: }
73:
74: // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
75:
76: void IOMedia::free(void)
77: {
78: //
79: // Free all of this object's outstanding resources.
80: //
81:
82: if (_openReaders) _openReaders->release();
83:
84: super::free();
85: }
86:
87: // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
88:
89: bool IOMedia::attachToChild(IORegistryEntry * client,
90: const IORegistryPlane * plane)
91: {
92: //
93: // This method is called for each client interested in the services we
94: // provide. The superclass links us as a parent to this client in the
95: // I/O Kit registry on success.
96: //
97:
98: OSString * s;
99:
100: // Ask our superclass' opinion.
101:
102: if (super::attachToChild(client, plane) == false) return false;
103:
104: //
105: // Determine whether the client is a storage object, which we consider
106: // to be a consumer of this storage object's content and a producer of
107: // new content. A storage object need not be an IOStorage subclass, so
108: // long as it identifies itself with a match category of "IOStorage".
109: //
110: // If the client is indeed a storage object, we reset the media's Leaf
111: // property to false and replace the media's Content property with the
112: // client's Content Mask property, if any.
113: //
114:
115: s = OSDynamicCast(OSString, client->getProperty(gIOMatchCategoryKey));
116:
117: if (s && !strcmp(s->getCStringNoCopy(), kIOStorageCategory))
118: {
119: setProperty(kIOMediaLeaf, false);
120:
121: s = OSDynamicCast(OSString, client->getProperty(kIOMediaContentMask));
122: if (s) setProperty(kIOMediaContent, s->getCStringNoCopy());
123: }
124:
125: return true;
126: }
127:
128: // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
129:
130: void IOMedia::detachFromChild(IORegistryEntry * client,
131: const IORegistryPlane * plane)
132: {
133: //
134: // This method is called for each client that loses interest in the
135: // services we provide. The superclass unlinks us from this client
136: // in the I/O Kit registry on success.
137: //
138: // Note that this method is called at a nondeterministic time after
139: // our client is terminated, which means another client may already
140: // have arrived and attached in the meantime. This is not an issue
141: // should the termination be issued synchrnously, however, which we
142: // take advantage of when this media needs to eliminate one of its
143: // clients. If the termination was issued on this media or farther
144: // below in the hierarchy, we don't really care that the properties
145: // would not be consistent since this media object is going to die
146: // anyway.
147: //
148:
149: OSString * s;
150:
151: //
152: // Determine whether the client is a storage object, which we consider
153: // to be a consumer of this storage object's content and a producer of
154: // new content. A storage object need not be an IOStorage subclass, so
155: // long as it identifies itself with a match category of "IOStorage".
156: //
157: // If the client is indeed a storage object, we reset the media's Leaf
158: // property to true and reset the media's Content property to the hint
159: // we obtained when this media was initialized.
160: //
161:
162: s = OSDynamicCast(OSString, client->getProperty(gIOMatchCategoryKey));
163:
164: if (s && !strcmp(s->getCStringNoCopy(), kIOStorageCategory))
165: {
166: setProperty(kIOMediaContent, getContentHint());
167: setProperty(kIOMediaLeaf, true);
168: }
169:
170: // Pass the call onto our superclass.
171:
172: super::detachFromChild(client, plane);
173: }
174:
175: // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
176:
177: bool IOMedia::handleOpen(IOService * client,
178: IOOptionBits options,
179: void * argument)
180: {
181: //
182: // The handleOpen method grants or denies permission to access this object
183: // to an interested client. The argument is an IOStorageAccess value that
184: // specifies the level of access desired -- reader or reader-writer.
185: //
186: // This method can be invoked to upgrade or downgrade the access level for
187: // an existing client as well. The previous access level will prevail for
188: // upgrades that fail, of course. A downgrade should never fail. If the
189: // new access level should be the same as the old for a given client, this
190: // method will do nothing and return success. In all cases, one, singular
191: // close-per-client is expected for all opens-per-client received.
192: //
193: // This method will work even when the media is in the terminated state.
194: //
195: // We are guaranteed that no other opens or closes will be processed until
196: // we make our decision, change our state, and return from this method.
197: //
198:
199: IOStorageAccess access = (IOStorageAccess) (int) argument;
200: IOStorageAccess level;
201:
202: assert(client);
203:
204: //
205: // Chart our course of action.
206: //
207:
208: switch (access)
209: {
210: case kAccessReader:
211: {
212: if (_openReaders->containsObject(client)) // (access: no change)
213: return true;
214: else if (_openReaderWriter == client) // (access: downgrade)
215: level = kAccessReader;
216: else // (access: new reader)
217: level = _openReaderWriter ? kAccessReaderWriter : kAccessReader;
218: break;
219: }
220: case kAccessReaderWriter:
221: {
222: if (_openReaders->containsObject(client)) // (access: upgrade)
223: level = kAccessReaderWriter;
224: else if (_openReaderWriter == client) // (access: no change)
225: return true;
226: else // (access: new writer)
227: level = kAccessReaderWriter;
228:
229: if (_isWritable == false) // (is this media object writable?)
230: return false;
231:
232: if (_openReaderWriter) // (does a reader-writer already exist?)
233: return false;
234:
235: break;
236: }
237: default:
238: {
239: assert(0);
240: return false;
241: }
242: }
243:
244: //
245: // If we are in the terminated state, we only accept downgrades.
246: //
247:
248: if (isInactive() && _openReaderWriter != client) // (dead? not a downgrade?)
249: return false;
250:
251: //
252: // Determine whether the storage objects above us can be torn down, should
253: // this be a new reader-writer open or an upgrade into a reader-writer (if
254: // the client issuing the open is not a storage object itself, of course).
255: //
256:
257: if (access == kAccessReaderWriter) // (a new reader-writer or an upgrade?)
258: {
259: const OSSymbol * category = OSSymbol::withCString(kIOStorageCategory);
260:
261: if (category)
262: {
263: IOService * storageObject = getClientWithCategory(category);
264: category->release();
265:
266: if (storageObject && storageObject != client)
267: {
268: if (storageObject->terminate(kIOServiceSynchronous) == false)
269: return false;
270: }
271: }
272: }
273:
274: //
275: // Determine whether the storage objects below us accept this open at this
276: // multiplexed level of access -- new opens, upgrades, and downgrades (and
277: // no changes in access) all enter through the same open api.
278: //
279:
280: if (_openLevel != level) // (has open level changed?)
281: {
282: IOStorage * provider = OSDynamicCast(IOStorage, getProvider());
283:
284: if (provider && provider->open(this, options, level) == false)
285: {
286: //
287: // We were unable to open the storage objects below us. We must
288: // recover from the terminate we issued above before bailing out,
289: // if applicable, by re-registering the media object for matching.
290: //
291:
292: if (access == kAccessReaderWriter)
293: registerService(kIOServiceSynchronous); // (re-register media)
294:
295: return false;
296: }
297: }
298:
299: //
300: // Process the open.
301: //
302: // We make sure our open state is consistent before calling registerService
303: // (if applicable) since this method can be called again on the same thread
304: // (the lock protecting handleOpen is recursive, so access would be given).
305: //
306:
307: _openLevel = level;
308:
309: if (access == kAccessReader)
310: {
311: _openReaders->setObject(client);
312:
313: if (_openReaderWriter == client) // (for a downgrade)
314: {
315: _openReaderWriter = 0;
316: registerService(kIOServiceSynchronous); // (re-register media)
317: }
318: }
319: else // (access == kAccessReaderWriter)
320: {
321: _openReaderWriter = client;
322:
323: _openReaders->removeObject(client); // (for an upgrade)
324: }
325:
326: return true;
327: }
328:
329: // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
330:
331: bool IOMedia::handleIsOpen(const IOService * client) const
332: {
333: //
334: // The handleIsOpen method determines whether the specified client, or any
335: // client if none is specificed, presently has an open on this object.
336: //
337: // This method will work even when the media is in the terminated state.
338: //
339: // We are guaranteed that no other opens or closes will be processed until
340: // we return from this method.
341: //
342:
343: if (client == 0) return (_openLevel != kAccessNone);
344:
345: return ( _openReaderWriter == client ||
346: _openReaders->containsObject(client) );
347: }
348:
349: // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
350:
351: void IOMedia::handleClose(IOService * client, IOOptionBits options)
352: {
353: //
354: // A client is informing us that it is giving up access to our contents.
355: //
356: // This method will work even when the media is in the terminated state.
357: //
358: // We are guaranteed that no other opens or closes will be processed until
359: // we change our state and return from this method.
360: //
361:
362: assert(client);
363:
364: //
365: // Process the close.
366: //
367:
368: bool reregister = (_openReaderWriter == client) && (isInactive() == false);
369:
370: if (_openReaderWriter == client) // (is the client a reader-writer?)
371: {
372: _openReaderWriter = 0;
373: }
374: else if (_openReaders->containsObject(client)) // (is the client a reader?)
375: {
376: _openReaders->removeObject(client);
377: }
378: else // (is the client is an imposter?)
379: {
380: assert(0);
381: return;
382: }
383:
384: //
385: // Reevaluate the open we have on the level below us. If no opens remain,
386: // we close, or if no reader-writer remains, but readers do, we downgrade.
387: //
388:
389: IOStorageAccess level;
390:
391: if (_openReaderWriter) level = kAccessReaderWriter;
392: else if (_openReaders->getCount()) level = kAccessReader;
393: else level = kAccessNone;
394:
395: if (_openLevel != level) // (has open level changed?)
396: {
397: IOStorage * provider = OSDynamicCast(IOStorage, getProvider());
398:
399: assert(level != kAccessReaderWriter);
400:
401: if (provider)
402: {
403: if (level == kAccessNone) // (is a close in order?)
404: {
405: provider->close(this, options);
406: }
407: else // (is a downgrade in order?)
408: {
409: bool success;
410: success = provider->open(this, 0, level);
411: assert(success); // (should never fail, unless avoided deadlock)
412: }
413: }
414:
415: _openLevel = level; // (set new open level)
416: }
417:
418: //
419: // If the reader-writer just closeed, re-register the media so that I/O Kit
420: // will attempt to match storage objects that may now be interested in this
421: // media.
422: //
423: // We make sure our open state is consistent before calling registerService
424: // (if applicable) since this method can be called again on the same thread
425: // (the lock protecting handleClose is recursive, so access would be given).
426: //
427:
428: if (reregister)
429: registerService(kIOServiceSynchronous); // (re-register media)
430: }
431:
432: // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
433:
434: void IOMedia::read(IOService * /* client */,
435: UInt64 byteStart,
436: IOMemoryDescriptor * buffer,
437: IOStorageCompletion completion)
438: {
439: //
440: // Read data from the storage object at the specified byte offset into the
441: // specified buffer, asynchronously. When the read completes, the caller
442: // will be notified via the specified completion action.
443: //
444: // The buffer will be retained for the duration of the read.
445: //
446: // This method will work even when the media is in the terminated state.
447: //
448:
449: if (isInactive())
450: {
451: complete(completion, kIOReturnNoMedia);
452: return;
453: }
454:
455: if (_openLevel == kAccessNone) // (instantaneous value, no lock)
456: {
457: complete(completion, kIOReturnNotOpen);
458: return;
459: }
460:
461: if (_mediaSize == 0 || _preferredBlockSize == 0)
462: {
463: complete(completion, kIOReturnUnformattedMedia);
464: return;
465: }
466:
467: if (_mediaSize < byteStart + buffer->getLength())
468: {
469: complete(completion, kIOReturnBadArgument);
470: return;
471: }
472:
473: byteStart += _mediaBase;
474: getProvider()->read(this, byteStart, buffer, completion);
475: }
476:
477: // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
478:
479: void IOMedia::write(IOService * client,
480: UInt64 byteStart,
481: IOMemoryDescriptor * buffer,
482: IOStorageCompletion completion)
483: {
484: //
485: // Write data into the storage object at the specified byte offset from the
486: // specified buffer, asynchronously. When the write completes, the caller
487: // will be notified via the specified completion action.
488: //
489: // The buffer will be retained for the duration of the write.
490: //
491: // This method will work even when the media is in the terminated state.
492: //
493:
494: if (isInactive())
495: {
496: complete(completion, kIOReturnNoMedia);
497: return;
498: }
499:
500: if (_openLevel == kAccessNone) // (instantaneous value, no lock)
501: {
502: complete(completion, kIOReturnNotOpen);
503: return;
504: }
505:
506: if (_openReaderWriter != client) // (instantaneous value, no lock)
507: {
508: ///m:2425148:workaround:commented:start
509: // complete(completion, kIOReturnNotPrivileged);
510: // return;
511: ///m:2425148:workaround:commented:stop
512: }
513:
514: if (_isWritable == 0)
515: {
516: complete(completion, kIOReturnLockedWrite);
517: return;
518: }
519:
520: if (_mediaSize == 0 || _preferredBlockSize == 0)
521: {
522: complete(completion, kIOReturnUnformattedMedia);
523: return;
524: }
525:
526: if (_mediaSize < byteStart + buffer->getLength())
527: {
528: complete(completion, kIOReturnBadArgument);
529: return;
530: }
531:
532: byteStart += _mediaBase;
533: getProvider()->write(this, byteStart, buffer, completion);
534: }
535:
536: // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
537:
538: UInt64 IOMedia::getPreferredBlockSize() const
539: {
540: //
541: // Ask the media object for its natural block size. This information
542: // is useful to clients that want to optimize access to the media.
543: //
544:
545: return _preferredBlockSize;
546: }
547:
548: // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
549:
550: UInt64 IOMedia::getSize() const
551: {
552: //
553: // Ask the media object for its total length in bytes.
554: //
555:
556: return _mediaSize;
557: }
558:
559: // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
560:
561: UInt64 IOMedia::getBase() const
562: {
563: //
564: // Ask the media object for its byte offset relative to its provider media
565: // object below it in the storage hierarchy.
566: //
567:
568: return _mediaBase;
569: }
570:
571: // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
572:
573: bool IOMedia::isEjectable() const
574: {
575: //
576: // Ask the media object whether it is ejectable.
577: //
578:
579: return _isEjectable;
580: }
581:
582: // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
583:
584: bool IOMedia::isFormatted() const
585: {
586: //
587: // Ask the media object whether it is formatted.
588: //
589:
590: return (_mediaSize && _preferredBlockSize);
591: }
592:
593: // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
594:
595: bool IOMedia::isWritable() const
596: {
597: //
598: // Ask the media object whether it is writable.
599: //
600:
601: return _isWritable;
602: }
603:
604: // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
605:
606: bool IOMedia::isWhole() const
607: {
608: //
609: // Ask the media object whether it represents the whole disk.
610: //
611:
612: return _isWhole;
613: }
614:
615: // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
616:
617: const char * IOMedia::getContent() const
618: {
619: //
620: // Ask the media object for a description of its contents. The description
621: // is the same as the hint at the time of the object's creation, but it is
622: // possible that the description be overrided by a client (which has probed
623: // the media and identified the content correctly) of the media object. It
624: // is more accurate than the hint for this reason. The string is formed in
625: // the likeness of Apple's "Apple_HFS" strings.
626: //
627: // The content description can be overrided by any client that matches onto
628: // this media object with a match category of kIOStorageCategory. The media
629: // object checks for a kIOMediaContentMask property in the client, and if it
630: // finds one, it copies it into kIOMediaContent property.
631: // property with it.
632: //
633:
634: OSString * string;
635:
636: string = OSDynamicCast(OSString, getProperty(kIOMediaContent));
637: if (string == 0) return "";
638: return string->getCStringNoCopy();
639: }
640:
641: // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
642:
643: const char * IOMedia::getContentHint() const
644: {
645: //
646: // Ask the media object for a hint of its contents. The hint is set at the
647: // time of the object's creation, should the creator have a clue as to what
648: // it may contain. The hint string does not change for the lifetime of the
649: // object and is also formed in the likeness of Apple's "Apple_HFS" strings.
650: //
651:
652: OSString * string;
653:
654: string = OSDynamicCast(OSString, getProperty(kIOMediaContentHint));
655: if (string == 0) return "";
656: return string->getCStringNoCopy();
657: }
658:
659: // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
660:
661: IOService * IOMedia::matchLocation(IOService * client)
662: {
663: //
664: // I/O Kit is in the process of searching for a candidate object that wishes
665: // to match against an IOLocation={x} dictionary of properties. This is the
666: // method called to determine whether this object wants to be the candidate.
667: //
668: // The matchLocation method should return "this" if it decides to match with
669: // the IOLocation={x} dictionary, otherwise it should call the superclass to
670: // continue with the search and skip this object as a candidate.
671: //
672: // If this object chooses to match, the dictionary {x} will be passed to the
673: // standard (passive) matching method matchPropertyTable for comparison.
674: //
675:
676: if (isWhole() == false)
677: {
678: // We elect to be the candidate object for the IOLocation={x} dictionary
679: // only if we are a non-whole media; whole medias have no "location" per
680: // se.
681:
682: assert(getLocation() && strcmp(getLocation(), ""));
683: return this; // ("please compare us")
684: }
685:
686: return super::matchLocation(client); // ("please skip over us")
687: }
688:
689: // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
690:
691: bool IOMedia::matchPropertyTable(OSDictionary * table)
692: {
693: //
694: // Compare the properties in the supplied table to this object's properties.
695: //
696:
697: OSString * string;
698:
699: // Ask our superclass' opinion.
700:
701: if (super::matchPropertyTable(table) == false) return false;
702:
703: // Determine whether the "IOPath Extension" property is in the comparison
704: // dictionary. If it is, we compare this media's location against it.
705:
706: string = OSDynamicCast(OSString, table->getObject("IOPath Extension"));
707:
708: if (string) // (does "IOPath Extension" exist?)
709: {
710: const char * location = getLocation();
711:
712: if (location == 0 && isWhole() == false) return false;
713:
714: UInt32 value1 = location ? strtoul(location, 0, 10) : 0;
715: UInt32 value2 = strtoul(string->getCStringNoCopy(), 0, 10);
716:
717: if (value1 == ULONG_MAX || value2 == ULONG_MAX) return false;
718: if (value1 != value2) return false;
719: }
720:
721: // Ensure the "IOProviderClass" property is present in the comparison
722: // dictionary, and that the given class name belongs to this object's
723: // inheritance chain.
724:
725: string = OSDynamicCast(OSString, table->getObject(gIOProviderClassKey));
726:
727: if (string == 0 || metaCast(string) == 0) return false;
728:
729: // We return success if the following expression is true -- individual
730: // comparisions evaluate to truth if the named property is not present
731: // in the supplied table.
732:
733: return compareProperty(table, kIOMediaContent) &&
734: compareProperty(table, kIOMediaContentHint) &&
735: compareProperty(table, kIOMediaEjectable) &&
736: compareProperty(table, kIOMediaLeaf) &&
737: compareProperty(table, kIOMediaSize) &&
738: compareProperty(table, kIOMediaWhole) &&
739: compareProperty(table, kIOMediaWritable) ;
740: }
741:
742: // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
743:
744: bool IOMedia::getPath( char * path,
745: int * length,
746: const IORegistryPlane * plane ) const
747: {
748: //
749: // Obtain a path to this service object.
750: //
751: // This method will work even when the media is in the terminated state.
752: //
753:
754: IOMedia * media;
755: IOService * next;
756: OSNumber * number;
757: bool success = false;
758: int unit = -1, partition = 0;
759: int len, maxlen;
760:
761: if ( isInactive() ) return false;
762:
763: if ( (plane == gIODTPlane) && length )
764: {
765: maxlen = *length;
766:
767: for (next = (IOService *) this; next; next = next->getProvider())
768: {
769: *length = maxlen;
770: media = OSDynamicCast(IOMedia, next);
771:
772: if ( media && media->isWhole() == false && media->getLocation() )
773: partition = strtoul(media->getLocation(), 0, 10);
774: else if ( (number = (OSNumber *) next->getProperty("IOUnit")) )
775: unit = number->unsigned32BitValue();
776: else if( !media && next->getPath( path, length, plane ))
777: break;
778: }
779:
780: if (next && (unit != -1) && (partition != -1)) {
781: // add the @unit:partition
782: len = *length;
783: maxlen -= len;
784: success = (maxlen > 20);
785: if( success ) {
786: len += sprintf( path + len, "/@%x:%d", unit, partition );
787: *length = len;
788: }
789: }
790:
791: }
792: else
793: {
794: success = super::getPath(path, length, plane);
795: }
796:
797: return success;
798: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.