1/* 2 * Copyright (c) 1998-2011 Apple Inc. All rights reserved. 3 * 4 * @APPLE_OSREFERENCE_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. The rights granted to you under the License 10 * may not be used to create, or enable the creation or redistribution of, 11 * unlawful or unlicensed copies of an Apple operating system, or to 12 * circumvent, violate, or enable the circumvention or violation of, any 13 * terms of an Apple operating system software license agreement. 14 * 15 * Please obtain a copy of the License at 16 * http://www.opensource.apple.com/apsl/ and read it before using this file. 17 * 18 * The Original Code and all software distributed under the License are 19 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER 20 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, 21 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, 22 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. 23 * Please see the License for the specific language governing rights and 24 * limitations under the License. 25 * 26 * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ 27 */ 28#include <IOKit/IOBSD.h> 29#include <IOKit/IOLib.h> 30#include <IOKit/IOService.h> 31#include <IOKit/IOCatalogue.h> 32#include <IOKit/IODeviceTreeSupport.h> 33#include <IOKit/IOKitKeys.h> 34#include <IOKit/IOPlatformExpert.h> 35 36extern "C" { 37 38#include <pexpert/pexpert.h> 39#include <kern/clock.h> 40#include <uuid/uuid.h> 41#include <sys/vnode_internal.h> 42 43// how long to wait for matching root device, secs 44#if DEBUG 45#define ROOTDEVICETIMEOUT 120 46#else 47#define ROOTDEVICETIMEOUT 60 48#endif 49 50extern dev_t mdevadd(int devid, uint64_t base, unsigned int size, int phys); 51extern dev_t mdevlookup(int devid); 52extern void mdevremoveall(void); 53extern void di_root_ramfile(IORegistryEntry * entry); 54 55kern_return_t 56IOKitBSDInit( void ) 57{ 58 IOService::publishResource("IOBSD"); 59 60 return( kIOReturnSuccess ); 61} 62 63void 64IOServicePublishResource( const char * property, boolean_t value ) 65{ 66 if ( value) 67 IOService::publishResource( property, kOSBooleanTrue ); 68 else 69 IOService::getResourceService()->removeProperty( property ); 70} 71 72boolean_t 73IOServiceWaitForMatchingResource( const char * property, uint64_t timeout ) 74{ 75 OSDictionary * dict = 0; 76 IOService * match = 0; 77 boolean_t found = false; 78 79 do { 80 81 dict = IOService::resourceMatching( property ); 82 if( !dict) 83 continue; 84 match = IOService::waitForMatchingService( dict, timeout ); 85 if ( match) 86 found = true; 87 88 } while( false ); 89 90 if( dict) 91 dict->release(); 92 if( match) 93 match->release(); 94 95 return( found ); 96} 97 98boolean_t 99IOCatalogueMatchingDriversPresent( const char * property ) 100{ 101 OSDictionary * dict = 0; 102 OSOrderedSet * set = 0; 103 SInt32 generationCount = 0; 104 boolean_t found = false; 105 106 do { 107 108 dict = OSDictionary::withCapacity(1); 109 if( !dict) 110 continue; 111 dict->setObject( property, kOSBooleanTrue ); 112 set = gIOCatalogue->findDrivers( dict, &generationCount ); 113 if ( set && (set->getCount() > 0)) 114 found = true; 115 116 } while( false ); 117 118 if( dict) 119 dict->release(); 120 if( set) 121 set->release(); 122 123 return( found ); 124} 125 126OSDictionary * IOBSDNameMatching( const char * name ) 127{ 128 OSDictionary * dict; 129 const OSSymbol * str = 0; 130 131 do { 132 133 dict = IOService::serviceMatching( gIOServiceKey ); 134 if( !dict) 135 continue; 136 str = OSSymbol::withCString( name ); 137 if( !str) 138 continue; 139 dict->setObject( kIOBSDNameKey, (OSObject *) str ); 140 str->release(); 141 142 return( dict ); 143 144 } while( false ); 145 146 if( dict) 147 dict->release(); 148 if( str) 149 str->release(); 150 151 return( 0 ); 152} 153 154OSDictionary * IOUUIDMatching( void ) 155{ 156 return IOService::resourceMatching( "boot-uuid-media" ); 157} 158 159OSDictionary * IONetworkNamePrefixMatching( const char * prefix ) 160{ 161 OSDictionary * matching; 162 OSDictionary * propDict = 0; 163 const OSSymbol * str = 0; 164 char networkType[128]; 165 166 do { 167 matching = IOService::serviceMatching( "IONetworkInterface" ); 168 if ( matching == 0 ) 169 continue; 170 171 propDict = OSDictionary::withCapacity(1); 172 if ( propDict == 0 ) 173 continue; 174 175 str = OSSymbol::withCString( prefix ); 176 if ( str == 0 ) 177 continue; 178 179 propDict->setObject( "IOInterfaceNamePrefix", (OSObject *) str ); 180 str->release(); 181 str = 0; 182 183 // see if we're contrained to netroot off of specific network type 184 if(PE_parse_boot_argn( "network-type", networkType, 128 )) 185 { 186 str = OSSymbol::withCString( networkType ); 187 if(str) 188 { 189 propDict->setObject( "IONetworkRootType", str); 190 str->release(); 191 str = 0; 192 } 193 } 194 195 if ( matching->setObject( gIOPropertyMatchKey, 196 (OSObject *) propDict ) != true ) 197 continue; 198 199 propDict->release(); 200 propDict = 0; 201 202 return( matching ); 203 204 } while ( false ); 205 206 if ( matching ) matching->release(); 207 if ( propDict ) propDict->release(); 208 if ( str ) str->release(); 209 210 return( 0 ); 211} 212 213static bool IORegisterNetworkInterface( IOService * netif ) 214{ 215 // A network interface is typically named and registered 216 // with BSD after receiving a request from a user space 217 // "namer". However, for cases when the system needs to 218 // root from the network, this registration task must be 219 // done inside the kernel and completed before the root 220 // device is handed to BSD. 221 222 IOService * stack; 223 OSNumber * zero = 0; 224 OSString * path = 0; 225 OSDictionary * dict = 0; 226 char * pathBuf = 0; 227 int len; 228 enum { kMaxPathLen = 512 }; 229 230 do { 231 stack = IOService::waitForService( 232 IOService::serviceMatching("IONetworkStack") ); 233 if ( stack == 0 ) break; 234 235 dict = OSDictionary::withCapacity(3); 236 if ( dict == 0 ) break; 237 238 zero = OSNumber::withNumber((UInt64) 0, 32); 239 if ( zero == 0 ) break; 240 241 pathBuf = (char *) IOMalloc( kMaxPathLen ); 242 if ( pathBuf == 0 ) break; 243 244 len = kMaxPathLen; 245 if ( netif->getPath( pathBuf, &len, gIOServicePlane ) 246 == false ) break; 247 248 path = OSString::withCStringNoCopy( pathBuf ); 249 if ( path == 0 ) break; 250 251 dict->setObject( "IOInterfaceUnit", zero ); 252 dict->setObject( kIOPathMatchKey, path ); 253 254 stack->setProperties( dict ); 255 } 256 while ( false ); 257 258 if ( zero ) zero->release(); 259 if ( path ) path->release(); 260 if ( dict ) dict->release(); 261 if ( pathBuf ) IOFree(pathBuf, kMaxPathLen); 262 263 return ( netif->getProperty( kIOBSDNameKey ) != 0 ); 264} 265 266OSDictionary * IOOFPathMatching( const char * path, char * buf, int maxLen ) 267{ 268 OSDictionary * matching = NULL; 269 OSString * str; 270 char * comp; 271 int len; 272 273 do { 274 275 len = strlen( kIODeviceTreePlane ":" ); 276 maxLen -= len; 277 if( maxLen <= 0) 278 continue; 279 280 strlcpy( buf, kIODeviceTreePlane ":", len + 1 ); 281 comp = buf + len; 282 283 len = strlen( path ); 284 maxLen -= len; 285 if( maxLen <= 0) 286 continue; 287 strlcpy( comp, path, len + 1 ); 288 289 matching = OSDictionary::withCapacity( 1 ); 290 if( !matching) 291 continue; 292 293 str = OSString::withCString( buf ); 294 if( !str) 295 continue; 296 matching->setObject( kIOPathMatchKey, str ); 297 str->release(); 298 299 return( matching ); 300 301 } while( false ); 302 303 if( matching) 304 matching->release(); 305 306 return( 0 ); 307} 308 309static int didRam = 0; 310enum { kMaxPathBuf = 512, kMaxBootVar = 128 }; 311 312kern_return_t IOFindBSDRoot( char * rootName, unsigned int rootNameSize, 313 dev_t * root, u_int32_t * oflags ) 314{ 315 mach_timespec_t t; 316 IOService * service; 317 IORegistryEntry * regEntry; 318 OSDictionary * matching = 0; 319 OSString * iostr; 320 OSNumber * off; 321 OSData * data = 0; 322 323 UInt32 flags = 0; 324 int mnr, mjr; 325 const char * mediaProperty = 0; 326 char * rdBootVar; 327 char * str; 328 const char * look = 0; 329 int len; 330 bool debugInfoPrintedOnce = false; 331 const char * uuidStr = NULL; 332 333 static int mountAttempts = 0; 334 335 int xchar, dchar; 336 337 338 if( mountAttempts++) 339 IOSleep( 5 * 1000 ); 340 341 str = (char *) IOMalloc( kMaxPathBuf + kMaxBootVar ); 342 if( !str) 343 return( kIOReturnNoMemory ); 344 rdBootVar = str + kMaxPathBuf; 345 346 if (!PE_parse_boot_argn("rd", rdBootVar, kMaxBootVar ) 347 && !PE_parse_boot_argn("rootdev", rdBootVar, kMaxBootVar )) 348 rdBootVar[0] = 0; 349 350 do { 351 if( (regEntry = IORegistryEntry::fromPath( "/chosen", gIODTPlane ))) { 352 di_root_ramfile(regEntry); 353 data = OSDynamicCast(OSData, regEntry->getProperty( "root-matching" )); 354 if (data) { 355 matching = OSDynamicCast(OSDictionary, OSUnserializeXML((char *)data->getBytesNoCopy())); 356 if (matching) { 357 continue; 358 } 359 } 360 361 data = (OSData *) regEntry->getProperty( "boot-uuid" ); 362 if( data) { 363 uuidStr = (const char*)data->getBytesNoCopy(); 364 OSString *uuidString = OSString::withCString( uuidStr ); 365 366 // match the boot-args boot-uuid processing below 367 if( uuidString) { 368 IOLog("rooting via boot-uuid from /chosen: %s\n", uuidStr); 369 IOService::publishResource( "boot-uuid", uuidString ); 370 uuidString->release(); 371 matching = IOUUIDMatching(); 372 mediaProperty = "boot-uuid-media"; 373 regEntry->release(); 374 continue; 375 } else { 376 uuidStr = NULL; 377 } 378 } 379 regEntry->release(); 380 } 381 } while( false ); 382 383// 384// See if we have a RAMDisk property in /chosen/memory-map. If so, make it into a device. 385// It will become /dev/mdx, where x is 0-f. 386// 387 388 if(!didRam) { /* Have we already build this ram disk? */ 389 didRam = 1; /* Remember we did this */ 390 if((regEntry = IORegistryEntry::fromPath( "/chosen/memory-map", gIODTPlane ))) { /* Find the map node */ 391 data = (OSData *)regEntry->getProperty("RAMDisk"); /* Find the ram disk, if there */ 392 if(data) { /* We found one */ 393 uintptr_t *ramdParms; 394 ramdParms = (uintptr_t *)data->getBytesNoCopy(); /* Point to the ram disk base and size */ 395 (void)mdevadd(-1, ml_static_ptovirt(ramdParms[0]) >> 12, ramdParms[1] >> 12, 0); /* Initialize it and pass back the device number */ 396 } 397 regEntry->release(); /* Toss the entry */ 398 } 399 } 400 401// 402// Now check if we are trying to root on a memory device 403// 404 405 if((rdBootVar[0] == 'm') && (rdBootVar[1] == 'd') && (rdBootVar[3] == 0)) { 406 dchar = xchar = rdBootVar[2]; /* Get the actual device */ 407 if((xchar >= '0') && (xchar <= '9')) xchar = xchar - '0'; /* If digit, convert */ 408 else { 409 xchar = xchar & ~' '; /* Fold to upper case */ 410 if((xchar >= 'A') && (xchar <= 'F')) { /* Is this a valid digit? */ 411 xchar = (xchar & 0xF) + 9; /* Convert the hex digit */ 412 dchar = dchar | ' '; /* Fold to lower case */ 413 } 414 else xchar = -1; /* Show bogus */ 415 } 416 if(xchar >= 0) { /* Do we have a valid memory device name? */ 417 *root = mdevlookup(xchar); /* Find the device number */ 418 if(*root >= 0) { /* Did we find one? */ 419 420 rootName[0] = 'm'; /* Build root name */ 421 rootName[1] = 'd'; /* Build root name */ 422 rootName[2] = dchar; /* Build root name */ 423 rootName[3] = 0; /* Build root name */ 424 IOLog("BSD root: %s, major %d, minor %d\n", rootName, major(*root), minor(*root)); 425 *oflags = 0; /* Show that this is not network */ 426 goto iofrootx; /* Join common exit... */ 427 } 428 panic("IOFindBSDRoot: specified root memory device, %s, has not been configured\n", rdBootVar); /* Not there */ 429 } 430 } 431 432 if( (!matching) && rdBootVar[0] ) { 433 // by BSD name 434 look = rdBootVar; 435 if( look[0] == '*') 436 look++; 437 438 if ( strncmp( look, "en", strlen( "en" )) == 0 ) { 439 matching = IONetworkNamePrefixMatching( "en" ); 440 } else if ( strncmp( look, "uuid", strlen( "uuid" )) == 0 ) { 441 char *uuid; 442 OSString *uuidString; 443 444 uuid = (char *)IOMalloc( kMaxBootVar ); 445 446 if ( uuid ) { 447 if (!PE_parse_boot_argn( "boot-uuid", uuid, kMaxBootVar )) { 448 panic( "rd=uuid but no boot-uuid=<value> specified" ); 449 } 450 uuidString = OSString::withCString( uuid ); 451 if ( uuidString ) { 452 IOService::publishResource( "boot-uuid", uuidString ); 453 uuidString->release(); 454 IOLog( "\nWaiting for boot volume with UUID %s\n", uuid ); 455 matching = IOUUIDMatching(); 456 mediaProperty = "boot-uuid-media"; 457 } 458 IOFree( uuid, kMaxBootVar ); 459 } 460 } else { 461 matching = IOBSDNameMatching( look ); 462 } 463 } 464 465 if( !matching) { 466 OSString * astring; 467 // Match any HFS media 468 469 matching = IOService::serviceMatching( "IOMedia" ); 470 astring = OSString::withCStringNoCopy("Apple_HFS"); 471 if ( astring ) { 472 matching->setObject("Content", astring); 473 astring->release(); 474 } 475 } 476 477 if( gIOKitDebug & kIOWaitQuietBeforeRoot ) { 478 IOLog( "Waiting for matching to complete\n" ); 479 IOService::getPlatform()->waitQuiet(); 480 } 481 482 if( true && matching) { 483 OSSerialize * s = OSSerialize::withCapacity( 5 ); 484 485 if( matching->serialize( s )) { 486 IOLog( "Waiting on %s\n", s->text() ); 487 s->release(); 488 } 489 } 490 491 do { 492 t.tv_sec = ROOTDEVICETIMEOUT; 493 t.tv_nsec = 0; 494 matching->retain(); 495 service = IOService::waitForService( matching, &t ); 496 if( (!service) || (mountAttempts == 10)) { 497 PE_display_icon( 0, "noroot"); 498 IOLog( "Still waiting for root device\n" ); 499 500 if( !debugInfoPrintedOnce) { 501 debugInfoPrintedOnce = true; 502 if( gIOKitDebug & kIOLogDTree) { 503 IOLog("\nDT plane:\n"); 504 IOPrintPlane( gIODTPlane ); 505 } 506 if( gIOKitDebug & kIOLogServiceTree) { 507 IOLog("\nService plane:\n"); 508 IOPrintPlane( gIOServicePlane ); 509 } 510 if( gIOKitDebug & kIOLogMemory) 511 IOPrintMemory(); 512 } 513 } 514 } while( !service); 515 matching->release(); 516 517 if ( service && mediaProperty ) { 518 service = (IOService *)service->getProperty(mediaProperty); 519 } 520 521 mjr = 0; 522 mnr = 0; 523 524 // If the IOService we matched to is a subclass of IONetworkInterface, 525 // then make sure it has been registered with BSD and has a BSD name 526 // assigned. 527 528 if ( service 529 && service->metaCast( "IONetworkInterface" ) 530 && !IORegisterNetworkInterface( service ) ) 531 { 532 service = 0; 533 } 534 535 if( service) { 536 537 len = kMaxPathBuf; 538 service->getPath( str, &len, gIOServicePlane ); 539 IOLog( "Got boot device = %s\n", str ); 540 541 iostr = (OSString *) service->getProperty( kIOBSDNameKey ); 542 if( iostr) 543 strlcpy( rootName, iostr->getCStringNoCopy(), rootNameSize ); 544 off = (OSNumber *) service->getProperty( kIOBSDMajorKey ); 545 if( off) 546 mjr = off->unsigned32BitValue(); 547 off = (OSNumber *) service->getProperty( kIOBSDMinorKey ); 548 if( off) 549 mnr = off->unsigned32BitValue(); 550 551 if( service->metaCast( "IONetworkInterface" )) 552 flags |= 1; 553 554 } else { 555 556 IOLog( "Wait for root failed\n" ); 557 strlcpy( rootName, "en0", rootNameSize ); 558 flags |= 1; 559 } 560 561 IOLog( "BSD root: %s", rootName ); 562 if( mjr) 563 IOLog(", major %d, minor %d\n", mjr, mnr ); 564 else 565 IOLog("\n"); 566 567 *root = makedev( mjr, mnr ); 568 *oflags = flags; 569 570 IOFree( str, kMaxPathBuf + kMaxBootVar ); 571 572iofrootx: 573 if( (gIOKitDebug & (kIOLogDTree | kIOLogServiceTree | kIOLogMemory)) && !debugInfoPrintedOnce) { 574 575 IOService::getPlatform()->waitQuiet(); 576 if( gIOKitDebug & kIOLogDTree) { 577 IOLog("\nDT plane:\n"); 578 IOPrintPlane( gIODTPlane ); 579 } 580 if( gIOKitDebug & kIOLogServiceTree) { 581 IOLog("\nService plane:\n"); 582 IOPrintPlane( gIOServicePlane ); 583 } 584 if( gIOKitDebug & kIOLogMemory) 585 IOPrintMemory(); 586 } 587 588 return( kIOReturnSuccess ); 589} 590 591bool IORamDiskBSDRoot(void) 592{ 593 char rdBootVar[kMaxBootVar]; 594 if (PE_parse_boot_argn("rd", rdBootVar, kMaxBootVar ) 595 || PE_parse_boot_argn("rootdev", rdBootVar, kMaxBootVar )) { 596 if((rdBootVar[0] == 'm') && (rdBootVar[1] == 'd') && (rdBootVar[3] == 0)) { 597 return true; 598 } 599 } 600 return false; 601} 602 603void IOSecureBSDRoot(const char * rootName) 604{ 605} 606 607void * 608IOBSDRegistryEntryForDeviceTree(char * path) 609{ 610 return (IORegistryEntry::fromPath(path, gIODTPlane)); 611} 612 613void 614IOBSDRegistryEntryRelease(void * entry) 615{ 616 IORegistryEntry * regEntry = (IORegistryEntry *)entry; 617 618 if (regEntry) 619 regEntry->release(); 620 return; 621} 622 623const void * 624IOBSDRegistryEntryGetData(void * entry, char * property_name, 625 int * packet_length) 626{ 627 OSData * data; 628 IORegistryEntry * regEntry = (IORegistryEntry *)entry; 629 630 data = (OSData *) regEntry->getProperty(property_name); 631 if (data) { 632 *packet_length = data->getLength(); 633 return (data->getBytesNoCopy()); 634 } 635 return (NULL); 636} 637 638kern_return_t IOBSDGetPlatformUUID( uuid_t uuid, mach_timespec_t timeout ) 639{ 640 IOService * resources; 641 OSString * string; 642 643 resources = IOService::waitForService( IOService::resourceMatching( kIOPlatformUUIDKey ), ( timeout.tv_sec || timeout.tv_nsec ) ? &timeout : 0 ); 644 if ( resources == 0 ) return KERN_OPERATION_TIMED_OUT; 645 646 string = ( OSString * ) IOService::getPlatform( )->getProvider( )->getProperty( kIOPlatformUUIDKey ); 647 if ( string == 0 ) return KERN_NOT_SUPPORTED; 648 649 uuid_parse( string->getCStringNoCopy( ), uuid ); 650 651 return KERN_SUCCESS; 652} 653 654kern_return_t IOBSDGetPlatformSerialNumber( char *serial_number_str, u_int32_t len ) 655{ 656 OSDictionary * platform_dict; 657 IOService *platform; 658 OSString * string; 659 660 if (len < 1) { 661 return 0; 662 } 663 serial_number_str[0] = '\0'; 664 665 platform_dict = IOService::serviceMatching( "IOPlatformExpertDevice" ); 666 if (platform_dict == NULL) { 667 return KERN_NOT_SUPPORTED; 668 } 669 670 platform = IOService::waitForService( platform_dict ); 671 if (platform) { 672 string = ( OSString * ) platform->getProperty( kIOPlatformSerialNumberKey ); 673 if ( string == 0 ) { 674 return KERN_NOT_SUPPORTED; 675 } else { 676 strlcpy( serial_number_str, string->getCStringNoCopy( ), len ); 677 } 678 } 679 680 return KERN_SUCCESS; 681} 682 683void IOBSDIterateMediaWithContent(const char *content_uuid_cstring, int (*func)(const char *bsd_dev_name, const char *uuid_str, void *arg), void *arg) 684{ 685 OSDictionary *dictionary; 686 OSString *content_uuid_string; 687 688 dictionary = IOService::serviceMatching( "IOMedia" ); 689 if( dictionary ) { 690 content_uuid_string = OSString::withCString( content_uuid_cstring ); 691 if( content_uuid_string ) { 692 IOService *service; 693 OSIterator *iter; 694 695 dictionary->setObject( "Content", content_uuid_string ); 696 dictionary->retain(); 697 698 iter = IOService::getMatchingServices(dictionary); 699 while (iter && (service = (IOService *)iter->getNextObject())) { 700 if( service ) { 701 OSString *iostr = (OSString *) service->getProperty( kIOBSDNameKey ); 702 OSString *uuidstr = (OSString *) service->getProperty( "UUID" ); 703 const char *uuid; 704 705 if( iostr) { 706 if (uuidstr) { 707 uuid = uuidstr->getCStringNoCopy(); 708 } else { 709 uuid = "00000000-0000-0000-0000-000000000000"; 710 } 711 712 // call the callback 713 if (func && func(iostr->getCStringNoCopy(), uuid, arg) == 0) { 714 break; 715 } 716 } 717 } 718 } 719 if (iter) 720 iter->release(); 721 722 content_uuid_string->release(); 723 } 724 dictionary->release(); 725 } 726} 727 728 729int IOBSDIsMediaEjectable( const char *cdev_name ) 730{ 731 int ret = 0; 732 OSDictionary *dictionary; 733 OSString *dev_name; 734 735 if (strncmp(cdev_name, "/dev/", 5) == 0) { 736 cdev_name += 5; 737 } 738 739 dictionary = IOService::serviceMatching( "IOMedia" ); 740 if( dictionary ) { 741 dev_name = OSString::withCString( cdev_name ); 742 if( dev_name ) { 743 IOService *service; 744 mach_timespec_t tv = { 5, 0 }; // wait up to "timeout" seconds for the device 745 746 dictionary->setObject( kIOBSDNameKey, dev_name ); 747 dictionary->retain(); 748 service = IOService::waitForService( dictionary, &tv ); 749 if( service ) { 750 OSBoolean *ejectable = (OSBoolean *) service->getProperty( "Ejectable" ); 751 752 if( ejectable ) { 753 ret = (int)ejectable->getValue(); 754 } 755 756 } 757 dev_name->release(); 758 } 759 dictionary->release(); 760 } 761 762 return ret; 763} 764 765} /* extern "C" */ 766