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