Transform.cpp
1 #include <CoreServices/CoreServices.h> 2 #include <Block.h> 3 #include <libkern/OSAtomic.h> 4 #include <syslog.h> 5 #include "Transform.h" 6 #include "StreamSource.h" 7 #include "SingleShotSource.h" 8 #include "Monitor.h" 9 #include "Utilities.h" 10 #include "c++utils.h" 11 #include "misc.h" 12 #include "SecTransformInternal.h" 13 #include "GroupTransform.h" 14 #include "GroupTransform.h" 15 #include <pthread.h> 16 17 static const int kMaxPendingTransactions = 20; 18 19 static CFTypeID internalID = _kCFRuntimeNotATypeID; 20 21 // Use &dispatchQueueToTransformKey as a key to dispatch_get_specific to map from 22 // a transforms master, activation, or any attribute queue to the Transform* 23 static unsigned char dispatchQueueToTransformKey; 24 25 static char RandomChar() 26 { 27 return arc4random() % 26 + 'A'; // good enough 28 } 29 30 31 static CFStringRef ah_set_describe(const void *v) CF_RETURNS_RETAINED { 32 transform_attribute *ta = ah2ta(static_cast<SecTransformAttributeRef>(const_cast<void*>(v))); 33 return CFStringCreateWithFormat(NULL, NULL, CFSTR("%@/%@=%@ (conn: %@)"), ta->transform->GetName(), ta->name, ta->value ? ta->value : CFSTR("NULL"), ta->connections ? static_cast<CFTypeRef>(ta->connections) : static_cast<CFTypeRef>(CFSTR("NONE"))); 34 } 35 36 static CFStringRef AttributeHandleFormat(CFTypeRef ah, CFDictionaryRef dict) CF_RETURNS_RETAINED { 37 transform_attribute *ta = ah2ta(ah); 38 return CFStringCreateWithFormat(NULL, NULL, CFSTR("%@/%@"), ta->transform->GetName(), ta->name); 39 } 40 41 static CFStringRef AttributeHandleDebugFormat(CFTypeRef ah) CF_RETURNS_RETAINED { 42 transform_attribute *ta = ah2ta(ah); 43 return CFStringCreateWithFormat(NULL, NULL, CFSTR("%@/%@ (%p)"), ta->transform->GetName(), ta->name, ta); 44 } 45 46 static void AttributeHandleFinalize(CFTypeRef ah) 47 { 48 transform_attribute *ta = ah2ta(ah); 49 if (!ta) 50 { 51 return; 52 } 53 54 if (ta->transform) 55 { 56 // When we release AH's we clear out the transform pointer, so if we get here with transform!=NULL somebody 57 // has released an AH we are still using and we will crash very very soon (in our code) if we don't abort here 58 syslog(LOG_ERR, "over release of SecTransformAttributeRef at %p\n", ah); 59 abort(); 60 } 61 62 if (ta->value) 63 { 64 CFReleaseNull(ta->value); 65 } 66 67 // ta->q already released 68 69 if (ta->connections) 70 { 71 CFReleaseNull(ta->connections); 72 } 73 74 if (ta->semaphore) 75 { 76 dispatch_release(ta->semaphore); 77 } 78 79 if (ta->attribute_changed_block) 80 { 81 Block_release(ta->attribute_changed_block); 82 } 83 84 if (ta->attribute_validate_block) 85 { 86 Block_release(ta->attribute_validate_block); 87 } 88 89 free(ta); 90 } 91 92 93 94 static CFHashCode ah_set_hash(const void *v) { 95 return CFHash(ah2ta(static_cast<SecTransformAttributeRef>(const_cast<void*>(v)))->name); 96 } 97 98 static Boolean ah_set_equal(const void *v1, const void *v2) { 99 return CFEqual(ah2ta(static_cast<SecTransformAttributeRef>(const_cast<void*>(v1)))->name, ah2ta(static_cast<SecTransformAttributeRef>(const_cast<void*>(v2)))->name); 100 } 101 102 CFTypeID transform_attribute::cftype; 103 104 SecTransformAttributeRef Transform::makeAH(transform_attribute *ta) { 105 if (ta) { 106 SecTransformAttributeRef ah = _CFRuntimeCreateInstance(NULL, transform_attribute::cftype, sizeof(struct transform_attribute*), NULL); 107 if (!ah) { 108 return NULL; 109 } 110 *(struct transform_attribute **)(1 + (CFRuntimeBase*)ah) = ta; 111 return ah; 112 } else { 113 return NULL; 114 } 115 } 116 117 static pthread_key_t ah_search_key_slot; 118 119 static void destroy_ah_search_key(void *ah) { 120 CFReleaseNull(ah); 121 pthread_setspecific(ah_search_key_slot, NULL); 122 } 123 124 125 126 SecTransformAttributeRef Transform::getAH(SecTransformStringOrAttributeRef attrib, bool create_ok, bool create_underscore_ok) 127 { 128 if (CFGetTypeID(attrib) == transform_attribute::cftype) 129 { 130 return (SecTransformAttributeRef)attrib; 131 } 132 133 CFStringRef label = (CFStringRef)attrib; 134 static dispatch_once_t once = 0; 135 const char *name = (const char *)"SecTransformAttributeRef"; 136 static CFRuntimeClass ahclass; 137 static CFSetCallBacks tasetcb; 138 139 dispatch_once(&once, ^{ 140 ahclass.className = name; 141 ahclass.copyFormattingDesc = AttributeHandleFormat; 142 ahclass.copyDebugDesc = AttributeHandleDebugFormat; 143 ahclass.finalize = AttributeHandleFinalize; 144 transform_attribute::cftype = _CFRuntimeRegisterClass(&ahclass); 145 if (transform_attribute::cftype == _kCFRuntimeNotATypeID) { 146 abort(); 147 } 148 149 tasetcb.equal = ah_set_equal; 150 tasetcb.hash = ah_set_hash; 151 tasetcb.copyDescription = ah_set_describe; 152 153 pthread_key_create(&ah_search_key_slot, destroy_ah_search_key); 154 }); 155 156 SecTransformAttributeRef search_for = pthread_getspecific(ah_search_key_slot); 157 if (!search_for) 158 { 159 transform_attribute* ta = (transform_attribute*)malloc(sizeof(transform_attribute)); 160 search_for = makeAH(ta); 161 if (!search_for) 162 { 163 free(ta); 164 return NULL; 165 } 166 167 bzero(ah2ta(search_for), sizeof(transform_attribute)); 168 pthread_setspecific(ah_search_key_slot, search_for); 169 } 170 171 if (!mAttributes) 172 { 173 mAttributes = CFSetCreateMutable(NULL, 0, &tasetcb); 174 } 175 176 ah2ta(search_for)->name = label; 177 SecTransformAttributeRef ah = static_cast<SecTransformAttributeRef>(const_cast<void*>(CFSetGetValue(mAttributes, search_for))); 178 if (ah == NULL && create_ok) 179 { 180 if (CFStringGetLength(label) && L'_' == CFStringGetCharacterAtIndex(label, 0) && !create_underscore_ok) 181 { 182 // Attributes with a leading _ belong to the Transform system only, not to random 3rd party transforms. 183 return NULL; 184 } 185 186 transform_attribute *ta = static_cast<transform_attribute *>(malloc(sizeof(transform_attribute))); 187 ah = makeAH(ta); 188 if (!ah) 189 { 190 free(ta); 191 return NULL; 192 } 193 194 ta->name = CFStringCreateCopy(NULL, label); 195 if (!ta->name) 196 { 197 CFRelease(ah); 198 free(ta); 199 return NULL; 200 } 201 CFIndex cnt = CFSetGetCount(mAttributes); 202 CFSetAddValue(mAttributes, ah); 203 if (CFSetGetCount(mAttributes) != cnt+1) 204 { 205 CFReleaseNull(ta->name); 206 free(ta); 207 CFRelease(ah); 208 return NULL; 209 } 210 211 CFStringRef qname = CFStringCreateWithFormat(NULL, NULL, CFSTR("%s/%@"), dispatch_queue_get_label(this->mDispatchQueue), label); 212 CFIndex used, sz = 1+CFStringGetMaximumSizeForEncoding(CFStringGetLength(qname), kCFStringEncodingUTF8); 213 UInt8 *qnbuf = (UInt8 *)alloca(sz); 214 CFStringGetBytes(qname, CFRangeMake(0, CFStringGetLength(qname)), kCFStringEncodingUTF8, '?', FALSE, qnbuf, sz, &used); 215 qnbuf[used] = '\0'; 216 ta->q = dispatch_queue_create((char*)qnbuf, NULL); 217 CFReleaseNull(qname); 218 ta->semaphore = dispatch_semaphore_create(kMaxPendingTransactions); 219 220 221 ta->pushback_state = transform_attribute::pb_empty; 222 ta->pushback_value = NULL; 223 ta->value = NULL; 224 ta->connections = NULL; 225 ta->transform = this; 226 227 dispatch_set_target_queue(ta->q, mDispatchQueue); 228 ta->required = 0; 229 ta->requires_outbound_connection = 0; 230 ta->deferred = 0; 231 ta->stream = 0; 232 ta->ignore_while_externalizing = 0; 233 ta->has_incoming_connection = 0; 234 ta->direct_error_handling = 0; 235 ta->allow_external_sets = 0; 236 ta->has_been_deferred = 0; 237 ta->attribute_changed_block = NULL; 238 ta->attribute_validate_block = NULL; 239 } 240 241 return ah; 242 } 243 244 transform_attribute *Transform::getTA(SecTransformStringOrAttributeRef attrib, bool create_ok) 245 { 246 SecTransformAttributeRef ah = getAH(attrib, create_ok); 247 if (ah) 248 { 249 return ah2ta(ah); 250 } 251 else 252 { 253 return NULL; 254 } 255 } 256 257 258 259 void Transform::TAGetAll(transform_attribute **attributes) { 260 CFSetGetValues(mAttributes, (const void**)attributes); 261 CFIndex i, n = CFSetGetCount(mAttributes); 262 for(i = 0; i < n; ++i) { 263 attributes[i] = ah2ta(attributes[i]); 264 } 265 } 266 267 268 269 bool Transform::HasNoOutboundConnections() 270 { 271 // make an array big enough to hold all of the attributes 272 CFIndex numAttributes = CFSetGetCount(mAttributes); 273 transform_attribute **attributes = (transform_attribute**)malloc(numAttributes*sizeof(transform_attribute*)); 274 275 if (attributes == NULL) { 276 // No more memory, we assume it's orphaned 277 return true; 278 } 279 280 TAGetAll(attributes); 281 282 // check all of the attributes 283 CFIndex i; 284 for (i = 0; i < numAttributes; ++i) 285 { 286 if (attributes[i]->connections && CFArrayGetCount(attributes[i]->connections) != 0) 287 { 288 free(attributes); 289 return false; 290 } 291 } 292 293 free(attributes); 294 295 return true; 296 } 297 298 299 300 bool Transform::HasNoInboundConnections() 301 { 302 // make an array big enough to hold all of the attributes 303 CFIndex numAttributes = CFSetGetCount(mAttributes); 304 transform_attribute **attributes = (transform_attribute**)malloc(numAttributes*sizeof(transform_attribute*)); 305 306 if (attributes == NULL) { 307 // No more memory, we assume it's orphaned 308 return true; 309 } 310 311 TAGetAll(attributes); 312 313 // check all of the attributes 314 CFIndex i; 315 for (i = 0; i < numAttributes; ++i) 316 { 317 if (attributes[i]->has_incoming_connection) 318 { 319 free(attributes); 320 return false; 321 } 322 } 323 324 free(attributes); 325 326 return true; 327 } 328 329 330 331 CFIndex Transform::GetAttributeCount() 332 { 333 return CFSetGetCount(mAttributes); 334 } 335 336 Transform::Transform(CFStringRef transformType, CFStringRef CFobjectType) : 337 CoreFoundationObject(CFobjectType), 338 mIsActive(false), 339 mIsFinalizing(false), 340 mAlwaysSelfNotify(false), 341 mGroup(NULL), 342 mAbortError(NULL), 343 mTypeName(CFStringCreateCopy(NULL, transformType)) 344 { 345 mAttributes = NULL; 346 mPushedback = NULL; 347 mProcessingPushbacks = FALSE; 348 349 if (internalID == _kCFRuntimeNotATypeID) { 350 (void)SecTransformNoData(); 351 internalID = CoreFoundationObject::FindObjectType(gInternalCFObjectName); 352 } 353 354 // create a name for the transform 355 char rname[10]; 356 unsigned i; 357 for (i = 0; i < sizeof(rname) - 1; ++i) 358 { 359 rname[i] = RandomChar(); 360 } 361 362 rname[i] = 0; 363 364 char *tname = const_cast<char*>(CFStringGetCStringPtr(transformType, kCFStringEncodingUTF8)); 365 if (!tname) { 366 CFIndex sz = 1+CFStringGetMaximumSizeForEncoding(CFStringGetLength(transformType), kCFStringEncodingUTF8); 367 tname = static_cast<typeof(tname)>(alloca(sz)); 368 if (tname) { 369 CFStringGetCString(transformType, tname, sz, kCFStringEncodingUTF8); 370 } else { 371 tname = const_cast<char*>("-"); 372 } 373 } 374 375 char* name; 376 asprintf(&name, "%s-%s", rname, tname); 377 378 char *dqName; 379 asprintf(&dqName, "%s-%s", rname, tname); 380 381 char *aqName; 382 asprintf(&aqName, "aq-%s-%s", rname, tname); 383 384 mDispatchQueue = dispatch_queue_create(dqName, NULL); 385 dispatch_queue_set_specific(mDispatchQueue, &dispatchQueueToTransformKey, this, NULL); 386 // mActivationQueue's job in life is to be suspended until just after this transform is made active. 387 // It's primary use is when a value flowing across a connection isn't sure if the target transform is active yet. 388 mActivationQueue = dispatch_queue_create(aqName, NULL); 389 dispatch_set_target_queue(mActivationQueue, mDispatchQueue); 390 dispatch_suspend(mActivationQueue); 391 mActivationPending = dispatch_group_create(); 392 393 // set up points for ABORT, DEBUG, INPUT, and OUTPUT 394 AbortAH = getAH(kSecTransformAbortAttributeName, true); 395 transform_attribute *ta = ah2ta(AbortAH); 396 ta->ignore_while_externalizing = 1; 397 CFStringRef attributeName = CFStringCreateWithCStringNoCopy(NULL, name, 0, kCFAllocatorMalloc); 398 SetAttributeNoCallback(kSecTransformTransformName, attributeName); 399 CFReleaseNull(attributeName); 400 401 free(dqName); 402 free(aqName); 403 404 DebugAH = getAH(kSecTransformDebugAttributeName, true); 405 ah2ta(DebugAH)->ignore_while_externalizing = 1; 406 407 ta = getTA(kSecTransformInputAttributeName, true); 408 ta->required = ta->deferred = ta->stream = 1; 409 ta->allow_external_sets = 0; 410 ta->value = NULL; 411 ta->has_been_deferred = 0; 412 ta = getTA(kSecTransformOutputAttributeName, true); 413 ta->requires_outbound_connection = ta->stream = 1; 414 } 415 416 static void run_and_release_finalizer(void *finalizer_block) 417 { 418 ((dispatch_block_t)finalizer_block)(); 419 Block_release(finalizer_block); 420 } 421 422 static void set_dispatch_finalizer(dispatch_object_t object, dispatch_block_t finalizer) 423 { 424 finalizer = Block_copy(finalizer); 425 dispatch_set_context(object, finalizer); 426 dispatch_set_finalizer_f(object, run_and_release_finalizer); 427 } 428 429 void Transform::FinalizePhase2() 430 { 431 delete this; 432 } 433 434 void Transform::FinalizeForClang() 435 { 436 CFIndex numAttributes = CFSetGetCount(mAttributes); 437 SecTransformAttributeRef *handles = (const void**)malloc(numAttributes*sizeof(SecTransformAttributeRef)); 438 439 if (handles == NULL) { 440 syslog(LOG_ERR, "Unable to allocate SecTransformAttributeRef handles in FinalizeForClang"); 441 return; 442 } 443 444 CFSetGetValues(mAttributes, handles); 445 446 for(CFIndex i = 0; i < numAttributes; ++i) { 447 SecTransformAttributeRef ah = handles[i]; 448 transform_attribute *ta = ah2ta(ah); 449 450 set_dispatch_finalizer(ta->q, ^{ 451 // NOTE: not done until all pending use of the attribute queue has ended AND retain count is zero 452 ta->transform = NULL; 453 CFReleaseSafe(ah); 454 }); 455 // If there is a pending pushback the attribute queue will be suspended, and needs a kick before it can be destructed. 456 if (__sync_bool_compare_and_swap(&ta->pushback_state, transform_attribute::pb_value, transform_attribute::pb_discard)) { 457 dispatch_resume(ta->q); 458 } 459 dispatch_release(ta->q); 460 } 461 462 // We might be finalizing a transform as it is being activated, make sure that is complete before we do the rest 463 dispatch_group_notify(mActivationPending, mDispatchQueue, ^{ 464 if (mActivationQueue != NULL) { 465 // This transform has not been activated (and does not have a activation pending), so we need to resume to activation queue before we can release it 466 dispatch_resume(mActivationQueue); 467 dispatch_release(mActivationQueue); 468 } 469 470 set_dispatch_finalizer(mDispatchQueue, ^{ 471 // NOTE: delayed until all pending work items on the transform's queue are complete, and all of the attribute queues have been finalized, and the retain count is zero 472 FinalizePhase2(); 473 }); 474 dispatch_release(mDispatchQueue); 475 }); 476 477 free(handles); 478 } 479 480 void Transform::Finalize() 481 { 482 // When _all_ transforms in the group have been marked as finalizing we can tear down our own context without anyone else in the group sending us values 483 // (NOTE: moved block into member function as clang hits an internal error and declines to compile) 484 dispatch_block_t continue_finalization = ^{ this->FinalizeForClang(); }; 485 dispatch_block_t mark_as_finalizing = ^{ this->mIsFinalizing = true; }; 486 487 // Mark the transform as "finalizing" so it knows not to propagate values across connections 488 if (this == dispatch_get_specific(&dispatchQueueToTransformKey)) { 489 mark_as_finalizing(); 490 } else { 491 dispatch_sync(mDispatchQueue, mark_as_finalizing); 492 } 493 494 if (mGroup) { 495 (void)transforms_assume(mGroup->mIsFinalizing); // under retain? 496 mGroup->AddAllChildrenFinalizedCallback(mDispatchQueue, continue_finalization); 497 mGroup->ChildStartedFinalization(this); 498 } else { 499 // a "bare" transform (normally itself a group) still needs to be deconstructed 500 dispatch_async(mDispatchQueue, continue_finalization); 501 } 502 } 503 504 Transform::~Transform() 505 { 506 CFReleaseNull(mAttributes); 507 if (mAbortError) { 508 CFReleaseNull(mAbortError); 509 mAbortError = NULL; 510 } 511 512 // See if we can catch anything using us after our death 513 mDispatchQueue = (dispatch_queue_t)0xdeadbeef; 514 515 CFReleaseNull(mTypeName); 516 517 if (NULL != mPushedback) 518 { 519 CFReleaseNull(mPushedback); 520 } 521 dispatch_release(mActivationPending); 522 } 523 524 CFStringRef Transform::GetName() CF_RETURNS_NOT_RETAINED { 525 return (CFStringRef)GetAttribute(kSecTransformTransformName); 526 } 527 528 CFTypeID Transform::GetCFTypeID() 529 { 530 return CoreFoundationObject::FindObjectType(CFSTR("SecTransform")); 531 } 532 533 std::string Transform::DebugDescription() 534 { 535 return CoreFoundationObject::DebugDescription() + "|SecTransform|" + StringFromCFString(this->GetName()); 536 } 537 538 CFErrorRef Transform::SendMetaAttribute(SecTransformStringOrAttributeRef key, SecTransformMetaAttributeType type, CFTypeRef value) 539 { 540 SecTransformAttributeRef ah = getAH(key, true); 541 transform_attribute *ta = ah2ta(ah); 542 switch (type) 543 { 544 case kSecTransformMetaAttributeRequired: 545 ta->required = CFBooleanGetValue((CFBooleanRef)value) ? 1 : 0; 546 break; 547 548 case kSecTransformMetaAttributeRequiresOutboundConnection: 549 ta->requires_outbound_connection = CFBooleanGetValue((CFBooleanRef)value) ? 1 : 0; 550 break; 551 552 case kSecTransformMetaAttributeDeferred: 553 ta->deferred = CFBooleanGetValue((CFBooleanRef)value) ? 1 : 0; 554 break; 555 556 case kSecTransformMetaAttributeStream: 557 ta->stream = CFBooleanGetValue((CFBooleanRef)value) ? 1 : 0; 558 break; 559 560 case kSecTransformMetaAttributeHasOutboundConnections: 561 return CreateSecTransformErrorRef(kSecTransformErrorInvalidOperation, "Can't set kSecTransformMetaAttributeHasOutboundConnections for %@ (or any other attribute)", ah); 562 563 case kSecTransformMetaAttributeHasInboundConnection: 564 return CreateSecTransformErrorRef(kSecTransformErrorInvalidOperation, "Can't set kSecTransformMetaAttributeHasInboundConnection for %@ (or any other attribute)", ah); 565 566 case kSecTransformMetaAttributeCanCycle: 567 return CreateSecTransformErrorRef(kSecTransformErrorInvalidOperation, "kSecTransformMetaAttributeCanCycle not yet supported (%@)", ah); 568 569 case kSecTransformMetaAttributeExternalize: 570 ta->ignore_while_externalizing = CFBooleanGetValue((CFBooleanRef)value) ? 0 : 1; 571 break; 572 573 case kSecTransformMetaAttributeValue: 574 return SetAttributeNoCallback(ah, value); 575 576 case kSecTransformMetaAttributeRef: 577 return CreateSecTransformErrorRef(kSecTransformErrorInvalidOperation, "Can't set kSecTransformMetaAttributeRef for %@ (or any other attribute)", ah); 578 579 case kSecTransformMetaAttributeName: 580 return CreateSecTransformErrorRef(kSecTransformErrorInvalidOperation, "Can't set kSecTransformMetaAttributeName for %@ (or any other attribute)", ah); 581 582 default: 583 return CreateSecTransformErrorRef(kSecTransformErrorInvalidOperation, "Can't set unknown meta attribute #%d to %@ on %@", type, value, key); 584 } 585 586 return NULL; 587 } 588 589 CFTypeRef Transform::GetMetaAttribute(SecTransformStringOrAttributeRef key, SecTransformMetaAttributeType type) { 590 SecTransformAttributeRef ah = getAH(key, true); 591 transform_attribute *ta = ah2ta(ah); 592 switch (type) { 593 case kSecTransformMetaAttributeRequired: 594 return (CFTypeRef)(ta->required ? kCFBooleanTrue : kCFBooleanFalse); 595 case kSecTransformMetaAttributeRequiresOutboundConnection: 596 return (CFTypeRef)(ta->requires_outbound_connection ? kCFBooleanTrue : kCFBooleanFalse); 597 case kSecTransformMetaAttributeDeferred: 598 return (CFTypeRef)(ta->deferred ? kCFBooleanTrue : kCFBooleanFalse); 599 case kSecTransformMetaAttributeStream: 600 return (CFTypeRef)(ta->stream ? kCFBooleanTrue : kCFBooleanFalse); 601 case kSecTransformMetaAttributeHasOutboundConnections: 602 return (CFTypeRef)((ta->connections && CFArrayGetCount(ta->connections)) ? kCFBooleanTrue : kCFBooleanFalse); 603 case kSecTransformMetaAttributeHasInboundConnection: 604 return (CFTypeRef)(ta->has_incoming_connection ? kCFBooleanTrue : kCFBooleanFalse); 605 case kSecTransformMetaAttributeCanCycle: 606 return (CFTypeRef)kCFBooleanFalse; 607 case kSecTransformMetaAttributeExternalize: 608 return (CFTypeRef)(ta->ignore_while_externalizing ? kCFBooleanFalse : kCFBooleanTrue); 609 case kSecTransformMetaAttributeRef: 610 return ah; 611 case kSecTransformMetaAttributeValue: 612 return ta->value; 613 case kSecTransformMetaAttributeName: 614 return ta->name; 615 default: 616 return CreateSecTransformErrorRef(kSecTransformErrorInvalidOperation, "Can't get unknown meta attribute #%d from %@", type, key); 617 } 618 619 return NULL; 620 } 621 622 623 624 CFErrorRef Transform::RefactorErrorToIncludeAbortingTransform(CFErrorRef sourceError) 625 { 626 // pull apart the error 627 CFIndex code = CFErrorGetCode(sourceError); 628 CFStringRef domain = CFErrorGetDomain(sourceError); 629 CFDictionaryRef oldUserInfo = CFErrorCopyUserInfo(sourceError); 630 CFMutableDictionaryRef userInfo = CFDictionaryCreateMutableCopy(NULL, 0, oldUserInfo); 631 CFReleaseNull(oldUserInfo); 632 633 // add the new key and value to the dictionary 634 CFDictionaryAddValue(userInfo, kSecTransformAbortOriginatorKey, GetCFObject()); 635 636 // make a new CFError 637 CFErrorRef newError = CFErrorCreate(NULL, domain, code, userInfo); 638 CFReleaseNull(userInfo); 639 return newError; 640 } 641 642 // NOTE: If called prior to execution will schedule a later call to AbortAllTransforms 643 void Transform::AbortJustThisTransform(CFErrorRef abortErr) 644 { 645 (void)transforms_assume(abortErr); 646 (void)transforms_assume(dispatch_get_specific(&dispatchQueueToTransformKey) == this); 647 648 Boolean wasActive = mIsActive; 649 650 if (OSAtomicCompareAndSwapPtr(NULL, (void *)abortErr, (void**)&mAbortError)) { 651 // send an abort message to the attribute so that it can shut down 652 // note that this bypasses the normal processes. The message sent is a notification 653 // that things aren't working well any more, the transform cannot make any other assumption. 654 655 // mAbortError is released in the destructor which is triggered (in part) 656 // by the dispatch queue finalizer so we don't need a retain/release of 657 // abortErr for the abortAction block, but we do need to retain it 658 // here to match with the release by the destructor. 659 CFRetainSafe(abortErr); 660 661 dispatch_block_t abortAction = ^{ 662 // This actually makes the abort happen, it needs to run on the transform's queue while the 663 // transform is executing. 664 665 if (!wasActive) { 666 // When this abort was first processed we were not executing, so 667 // additional transforms may have been added to our group (indeed, 668 // we may not have had a group at all), so we need to let everyone 669 // know about the problem. This will end up letting ourself (and 670 // maybe some others) know an additional time, but the CompareAndSwap 671 // prevents that from being an issue. 672 this->AbortAllTransforms(abortErr); 673 } 674 675 SecTransformAttributeRef GCC_BUG_WORKAROUND inputAttributeHandle = getAH(kSecTransformInputAttributeName, false); 676 // Calling AttributeChanged directly lets an error "skip ahead" of the input queue, 677 // and even execute if the input queue is suspended pending pushback retries. 678 AttributeChanged(inputAttributeHandle, abortErr); 679 try_pushbacks(); 680 }; 681 682 if (mIsActive) { 683 // This transform is running, so we use the normal queue (which we are 684 // already executing on) 685 abortAction(); 686 } else { 687 // This transform hasn't run yet, do the work on the activation queue 688 // so it happens as soon as the transforms starts executing. 689 dispatch_async(mActivationQueue, abortAction); 690 } 691 } else { 692 Debug("%@ set to %@ while processing ABORT=%@, this extra set will be ignored", AbortAH, abortErr, mAbortError); 693 } 694 } 695 696 // abort all transforms in the root group & below 697 void Transform::AbortAllTransforms(CFTypeRef err) 698 { 699 Debug("%@ set to %@, aborting\n", AbortAH, err); 700 CFErrorRef error = NULL; 701 702 CFTypeRef replacementErr = NULL; 703 704 if (CFGetTypeID(err) != CFErrorGetTypeID()) 705 { 706 CFStringRef thisErrorTypeDescription = CFCopyTypeIDDescription(CFGetTypeID(err)); 707 CFStringRef regularErrorTypeDescription = CFCopyTypeIDDescription(CFErrorGetTypeID()); 708 replacementErr = err = CreateSecTransformErrorRef(kSecTransformErrorInvalidType, "ABORT set to a %@ (%@) not a %@", thisErrorTypeDescription, err, regularErrorTypeDescription); 709 CFReleaseNull(thisErrorTypeDescription); 710 CFReleaseNull(regularErrorTypeDescription); 711 } 712 713 error = RefactorErrorToIncludeAbortingTransform((CFErrorRef)err); 714 715 if (replacementErr) 716 { 717 CFReleaseNull(replacementErr); 718 } 719 720 GroupTransform *root = GetRootGroup(); 721 if (root) 722 { 723 // tell everyone in the (root) group to "AbortJustThisTransform" 724 dispatch_group_t all_aborted = dispatch_group_create(); 725 root->ForAllNodesAsync(false, all_aborted, ^(Transform* t){ 726 t->AbortJustThisTransform(error); 727 }); 728 dispatch_group_notify(all_aborted, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^(void) { 729 CFReleaseSafe(error); 730 dispatch_release(all_aborted); 731 }); 732 } 733 else 734 { 735 // We are everyone so we AbortJustThisTransform "directly" 736 // NOTE: this can only happen prior to execution (execution always happens in a group) 737 (void)transforms_assume_zero(mIsActive); 738 this->AbortJustThisTransform(error); 739 } 740 } 741 742 743 744 CFErrorRef Transform::Disconnect(Transform* destinationTransform, CFStringRef myKey, CFStringRef hisKey) 745 { 746 //CFTypeRef thisTransform = (SecTransformRef) GetCFObject(); 747 748 // find this transform in the backlinks for the destination 749 CFIndex i; 750 751 // now remove the link in the transform dictionary 752 transform_attribute *src = getTA(myKey, true); 753 SecTransformAttributeRef dst = destinationTransform->getAH(hisKey); 754 755 if (src->connections == NULL) 756 { 757 return CreateSecTransformErrorRef(kSecTransformErrorInvalidOperation, "Cannot find transform in destination."); 758 } 759 760 CFIndex numConnections = CFArrayGetCount(src->connections); 761 for (i = 0; i < numConnections; ++i) 762 { 763 if (CFArrayGetValueAtIndex(src->connections, i) == dst) 764 { 765 CFArrayRemoveValueAtIndex(src->connections, i); 766 numConnections = CFArrayGetCount(src->connections); 767 } 768 769 // clear the has_incoming_connection bit in the destination. We can do this because inputs can have only one connection. 770 transform_attribute* dstTA = ah2ta(dst); 771 dstTA->has_incoming_connection = false; 772 } 773 774 if (HasNoInboundConnections() && HasNoOutboundConnections()) 775 { 776 // we have been orphaned, just remove us 777 mGroup->RemoveMemberFromGroup(GetCFObject()); 778 mGroup = NULL; 779 } 780 781 return NULL; 782 } 783 784 785 786 CFErrorRef Transform::Connect(GroupTransform *group, Transform* destinationTransform, CFStringRef destAttr, CFStringRef srcAttr) 787 { 788 if (group == NULL) 789 { 790 CFErrorRef err = CreateSecTransformErrorRef(kSecTransformErrorInvalidConnection, "Can not make connections without a specific group (do not call with group = NULL)"); 791 return err; 792 } 793 794 GroupTransform *newSourceGroup = mGroup; 795 GroupTransform *newDestinationGroup = destinationTransform->mGroup; 796 797 if (mGroup == NULL || mGroup == this) 798 { 799 newSourceGroup = group; 800 } 801 802 if (destinationTransform->mGroup == NULL || destinationTransform->mGroup == destinationTransform) 803 { 804 newDestinationGroup = group; 805 } 806 807 if (newSourceGroup != newDestinationGroup && mGroup) 808 { 809 CFErrorRef err = CreateSecTransformErrorRef(kSecTransformErrorInvalidConnection, "Can not make connections between transforms in different groups (%@ is in %@, %@ is in %@)", GetName(), newSourceGroup->GetName(), destinationTransform->GetName(), newDestinationGroup->GetName()); 810 return err; 811 } 812 813 if (!validConnectionPoint(srcAttr)) { 814 CFErrorRef err = CreateSecTransformErrorRef(kSecTransformErrorInvalidConnection, "Can not make a connection from non-exported attribute %@ of %@", srcAttr, this->GetName()); 815 return err; 816 } 817 if (!destinationTransform->validConnectionPoint(destAttr)) { 818 CFErrorRef err = CreateSecTransformErrorRef(kSecTransformErrorInvalidConnection, "Can not make a connection to non-exported attribute %@ of %@", destAttr, destinationTransform->GetName()); 819 return err; 820 } 821 822 mGroup = newSourceGroup; 823 destinationTransform->mGroup = newDestinationGroup; 824 825 // NOTE: this fails on OOM 826 group->AddMemberToGroup(this->GetCFObject()); 827 group->AddMemberToGroup(destinationTransform->GetCFObject()); 828 829 transform_attribute *src = this->getTA(srcAttr, true); 830 SecTransformAttributeRef dst = destinationTransform->getAH(destAttr); 831 832 if (!src->connections) 833 { 834 src->connections = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); 835 } 836 CFArrayAppendValue(src->connections, dst); 837 838 ah2ta(dst)->has_incoming_connection = 1; 839 840 return NULL; 841 } 842 843 844 bool Transform::validConnectionPoint(CFStringRef attributeName) 845 { 846 return true; 847 } 848 849 // NoCallback == don't call this transform's Do function, but DO call the Do functions of connected attributes 850 // SetAttribute eventually calls SetAttributeNoCallback 851 CFErrorRef Transform::SetAttributeNoCallback(SecTransformStringOrAttributeRef key, CFTypeRef value) 852 { 853 SecTransformAttributeRef ah = getAH(key, true); 854 if (!ah) 855 { 856 abort(); 857 } 858 transform_attribute *ta = ah2ta(ah); 859 860 if (ah == AbortAH && value && (mIsActive || !ta->deferred)) 861 { 862 AbortAllTransforms(value); 863 return CreateSecTransformErrorRef(kSecTransformErrorAbortInProgress, "Abort started"); 864 } 865 866 bool do_propagate = true; 867 868 if (!ta->has_been_deferred) 869 { 870 bool doNotRetain = false; 871 872 if (value) 873 { 874 CFStringRef name = ta->name; 875 if (CFGetTypeID(value) == CFReadStreamGetTypeID()) 876 { 877 CFTypeRef src = StreamSource::Make((CFReadStreamRef) value, this, name); 878 value = src; 879 do_propagate = false; 880 ta->has_been_deferred = 1; 881 doNotRetain = true; 882 } 883 else if (ta->deferred && !mIsActive) 884 { 885 if (ta->deferred) 886 { 887 Debug("%@ deferred value=%p\n", ah, value); 888 } 889 890 CFTypeRef src = SingleShotSource::Make(value, this, name); 891 ta->has_been_deferred = 1; 892 893 // the old value will be release when Transform::Do terminates 894 895 value = src; 896 do_propagate = false; 897 doNotRetain = true; 898 } 899 else 900 { 901 ta->has_been_deferred = 0; 902 } 903 } 904 905 if (ta->value != value) { 906 if (value && !doNotRetain) { 907 CFRetainSafe(value); 908 } 909 if (ta->value) { 910 CFReleaseNull(ta->value); 911 } 912 } 913 914 ta->value = value; 915 } 916 917 // propagate the changes out to all connections 918 if (ta->connections && mIsActive && do_propagate && !(mAbortError || mIsFinalizing)) 919 { 920 Debug("Propagating from %@ to %@\n", ah, ta->connections); 921 CFIndex i, numConnections = CFArrayGetCount(ta->connections); 922 for(i = 0; i < numConnections; ++i) { 923 SecTransformAttributeRef ah = static_cast<SecTransformAttributeRef>(const_cast<void *>(CFArrayGetValueAtIndex(ta->connections, i))); 924 Transform *tt = ah2ta(ah)->transform; 925 if (NULL != tt) 926 { 927 if (tt->mIsActive) 928 { 929 tt->SetAttribute(ah, value); 930 } 931 else 932 { 933 dispatch_block_t setAttribute = ^{ 934 tt->SetAttribute(ah, value); 935 }; 936 // Here the target queue might not be activated yet, we can't 937 // look directly at the other transform's ActivationQueue as 938 // it might activate (or Finalize!) as we look, so just ask 939 // the other transform to deal with it. 940 dispatch_async(ah2ta(ah)->q, ^(void) { 941 // This time we are on the right queue to know this is the real deal 942 if (tt->mIsActive) { 943 setAttribute(); 944 } else { 945 dispatch_async(ah2ta(ah)->transform->mActivationQueue, setAttribute); 946 } 947 }); 948 } 949 } 950 } 951 } 952 953 return NULL; 954 } 955 956 // external sets normally fail if the transform is running 957 CFErrorRef Transform::ExternalSetAttribute(CFTypeRef key, CFTypeRef value) 958 { 959 if (!mIsActive) 960 { 961 return this->SetAttribute(key, value); 962 } 963 else 964 { 965 SecTransformAttributeRef ah = getAH(key, false); 966 if (ah != NULL && ah2ta(ah)->allow_external_sets) 967 { 968 return this->SetAttribute(static_cast<CFTypeRef>(ah), value); 969 } 970 else 971 { 972 return CreateSecTransformErrorRef(kSecTransformTransformIsExecuting, "%@ can not be set while %@ is executing", ah, this->GetName()); 973 } 974 } 975 } 976 977 978 // queue up the setting of the key and value 979 CFErrorRef Transform::SetAttribute(CFTypeRef key, CFTypeRef value) 980 { 981 if (mAbortError) 982 { 983 CFErrorRef result = CreateSecTransformErrorRef(kSecTransformErrorAborted, "ABORT has been sent to the transform (%@)", mAbortError); 984 CFAutorelease(result); 985 return result; 986 } 987 988 // queue up the setting of the key and value 989 SecTransformAttributeRef ah; 990 if (CFGetTypeID(key) == transform_attribute::cftype) 991 { 992 ah = key; 993 } 994 else if (CFGetTypeID(key) == CFStringGetTypeID()) 995 { 996 ah = getAH(static_cast<CFStringRef>(key)); 997 if (!ah) 998 { 999 CFErrorRef result = CreateSecTransformErrorRef(kSecTransformErrorUnsupportedAttribute, "Can't set attribute %@ in transform %@", key, GetName()); 1000 CFAutorelease(result); 1001 return result; 1002 } 1003 } 1004 else 1005 { 1006 CFErrorRef result = CreateSecTransformErrorRef(kSecTransformErrorInvalidType, "Transform::SetAttribute called with %@, requires a string or an AttributeHandle", key); 1007 CFAutorelease(result); 1008 return result; 1009 } 1010 1011 // Do this after the error check above so we don't leak 1012 CFRetainSafe(value); // if we use dispatch_async we need to own the value (the matching release is in the set block) 1013 1014 transform_attribute *ta = ah2ta(ah); 1015 1016 dispatch_block_t set = ^{ 1017 Do(ah, value); 1018 dispatch_semaphore_signal(ta->semaphore); 1019 CFReleaseSafe(value); 1020 }; 1021 1022 1023 // when the transform is active, set attributes asynchronously. Otherwise, we are doing 1024 // initialization and must wait for the operation to complete. 1025 if (mIsActive) 1026 { 1027 dispatch_async(ta->q, set); 1028 } 1029 else 1030 { 1031 dispatch_sync(ta->q, set); 1032 } 1033 if (dispatch_semaphore_wait(ta->semaphore, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC))) { 1034 Debug("Send from %@ to %@ is still waiting\n", GetName(), ah); 1035 dispatch_semaphore_wait(ta->semaphore, DISPATCH_TIME_FOREVER); 1036 } 1037 1038 // Return the best available status (which will be NULL if we haven't aborted, or stated an 1039 // intent to abort when execution starts) 1040 // 1041 // The value of the ABORT attribute can differ from mAbortError, first if a transform is aborted 1042 // prior to running the general abort mechanic is deferred until execution. Second during 1043 // execution the abort logic avoids most of the normal processing. Third, and most importantly 1044 // during an abort the exact error that gets generated will differ from the value sent to ABORT 1045 // (for example if a non-CFError was sent...plus even if it was a CFError we annotate that error). 1046 1047 return mAbortError; 1048 } 1049 1050 CFErrorRef Transform::SendAttribute(SecTransformStringOrAttributeRef key, CFTypeRef value) 1051 { 1052 return SetAttributeNoCallback(key, value); 1053 } 1054 1055 1056 1057 CFTypeRef Transform::GetAttribute(SecTransformStringOrAttributeRef key) 1058 { 1059 struct transform_attribute *ta = getTA(key, false); 1060 if (ta == NULL || ta->value == NULL) { 1061 return NULL; 1062 } 1063 1064 if (CFGetTypeID(ta->value) == internalID) 1065 { 1066 // this is one of our internal objects, so get the value from it 1067 Source* source = (Source*) CoreFoundationHolder::ObjectFromCFType(ta->value); 1068 return source->GetValue(); 1069 } 1070 else 1071 { 1072 return ta->value; 1073 } 1074 } 1075 1076 CFErrorRef Transform::Pushback(SecTransformAttributeRef ah, CFTypeRef value) 1077 { 1078 CFErrorRef result = NULL; 1079 transform_attribute *ta = ah2ta(ah); 1080 if (!(ta->pushback_state == transform_attribute::pb_empty || ta->pushback_state == transform_attribute::pb_repush)) 1081 { 1082 CFErrorRef error = fancy_error(kSecTransformErrorDomain, kSecTransformErrorInvalidOperation, CFSTR("Can not pushback new value until old value has been processed")); 1083 SetAttribute(kSecTransformAbortAttributeName, error); 1084 return error; 1085 } 1086 if (value == NULL && ta->pushback_value == NULL && ta->pushback_state == transform_attribute::pb_repush) 1087 { 1088 ta->pushback_state = transform_attribute::pb_presented_once; 1089 } else 1090 { 1091 ta->pushback_state = transform_attribute::pb_value; 1092 } 1093 if (value) 1094 { 1095 CFRetainSafe(value); 1096 } 1097 ta->pushback_value = value; 1098 dispatch_suspend(ta->q); 1099 if (!mPushedback) 1100 { 1101 mPushedback = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); 1102 } 1103 CFArrayAppendValue(mPushedback, ah); 1104 return result; 1105 } 1106 1107 void Transform::try_pushbacks() { 1108 if (!mPushedback || !CFArrayGetCount(mPushedback)) { 1109 mProcessingPushbacks = FALSE; 1110 return; 1111 } 1112 1113 CFArrayRef pb = (CFArrayRef)mPushedback; 1114 mPushedback = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); 1115 CFIndex i, n = CFArrayGetCount(pb); 1116 int succeeded = 0; 1117 for(i = 0; i < n; ++i) 1118 { 1119 SecTransformAttributeRef ah = CFArrayGetValueAtIndex(pb, i); 1120 transform_attribute *ta = ah2ta(ah); 1121 ta->pushback_state = transform_attribute::pb_repush; 1122 CFTypeRef v = ta->pushback_value; 1123 ta->pushback_value = NULL; 1124 Do(ah, v); 1125 if (v) 1126 { 1127 CFReleaseNull(v); 1128 } 1129 if (ta->pushback_state == transform_attribute::pb_repush) { 1130 ta->pushback_state = transform_attribute::pb_empty; 1131 succeeded++; 1132 } 1133 // NOTE: a successful repush needs the queue unsuspended so it can run. 1134 // A failed repush has suspended the queue an additional time, so we 1135 // still need to resume it. 1136 dispatch_resume(ta->q); 1137 } 1138 1139 CFReleaseNull(pb); 1140 1141 if (succeeded && CFArrayGetCount(mPushedback)) { 1142 // some attribute changed while we proceeded the last batch of pushbacks, so any "new" pushbacks are eligible to run again. 1143 // In theory the ones that were pushed after the last success don't need to be re-run but that isn't a big deal. 1144 dispatch_async(mDispatchQueue, ^{ try_pushbacks(); }); 1145 } else { 1146 mProcessingPushbacks = FALSE; 1147 } 1148 } 1149 1150 void Transform::Debug(const char *cfmt, ...) { 1151 CFTypeRef d = ah2ta(DebugAH)->value; 1152 if (d) { 1153 CFWriteStreamRef out = NULL; 1154 if (CFGetTypeID(d) == CFWriteStreamGetTypeID()) { 1155 out = (CFWriteStreamRef)d; 1156 } else { 1157 static dispatch_once_t once; 1158 static CFWriteStreamRef StdErrWriteStream; 1159 dispatch_once(&once, ^{ 1160 CFURLRef p = CFURLCreateWithFileSystemPath(NULL, CFSTR("/dev/stderr"), kCFURLPOSIXPathStyle, FALSE); 1161 StdErrWriteStream = CFWriteStreamCreateWithFile(NULL, p); 1162 CFWriteStreamOpen(StdErrWriteStream); 1163 CFReleaseNull(p); 1164 }); 1165 out = StdErrWriteStream; 1166 } 1167 1168 va_list ap; 1169 va_start(ap, cfmt); 1170 1171 CFStringRef fmt = CFStringCreateWithCString(NULL, cfmt, kCFStringEncodingUTF8); 1172 CFStringRef str = CFStringCreateWithFormatAndArguments(NULL, NULL, fmt, ap); 1173 CFReleaseNull(fmt); 1174 va_end(ap); 1175 1176 1177 CFIndex sz = CFStringGetMaximumSizeForEncoding(CFStringGetLength(str), kCFStringEncodingUTF8); 1178 sz += 1; 1179 CFIndex used = 0; 1180 unsigned char *buf; 1181 bool needs_free = true; 1182 buf = (unsigned char*)malloc(sz); 1183 if (buf) { 1184 CFStringGetBytes(str, CFRangeMake(0, CFStringGetLength(str)), kCFStringEncodingUTF8, '?', FALSE, buf, sz, &used); 1185 } else { 1186 buf = (unsigned char *)"malloc failure during Transform::Debug\n"; 1187 needs_free = false; 1188 } 1189 1190 static dispatch_once_t once; 1191 static dispatch_queue_t print_q; 1192 dispatch_once(&once, ^{ 1193 print_q = dispatch_queue_create("com.apple.security.debug.print_queue", 0); 1194 dispatch_set_target_queue((dispatch_object_t)print_q, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0)); 1195 }); 1196 1197 dispatch_async(print_q, ^{ 1198 CFWriteStreamWrite(out, buf, used); 1199 if (needs_free) { 1200 free(buf); 1201 } 1202 }); 1203 1204 CFReleaseNull(str); 1205 } 1206 } 1207 1208 void Transform::Do(SecTransformAttributeRef ah, CFTypeRef value) 1209 { 1210 transform_attribute *ta = ah2ta(ah); 1211 if (ta->pushback_state == transform_attribute::pb_discard) 1212 { 1213 return; 1214 } 1215 (void)transforms_assume(dispatch_get_current_queue() == ((ta->pushback_state == transform_attribute::pb_repush) ? mDispatchQueue : ta->q)); 1216 1217 if (mIsFinalizing) 1218 { 1219 Debug("Ignoring value %p sent to %@ (on queue %s) during finalization", value, ah, dispatch_queue_get_label(dispatch_get_current_queue())); 1220 return; 1221 } 1222 1223 SetAttributeNoCallback(ah, value); 1224 // While an abort is in progress things can get into bad 1225 // states if we allow normal processing so we throw anything 1226 // on the floor except CFErrorRef or NULL vales sent to 1227 // ABORT or INPUT (we need to process them to let the 1228 // transform shut down correctly) 1229 if (mAbortError && (!(ah == this->AbortAH || ah == getTA(CFSTR("INPUT"), true)) && (value == NULL || CFGetTypeID(value) != CFErrorGetTypeID()))) 1230 { 1231 if (value) { 1232 Debug("Ignoring value (%@) sent to %@ during abort\n", value, ah); 1233 } else { 1234 Debug("Ignoring NULL sent to %@ during abort\n", ah); 1235 } 1236 return; 1237 } 1238 1239 if (mIsActive || (mAlwaysSelfNotify && !ta->deferred)) 1240 { 1241 Debug("AttributeChanged: %@ (%s) = %@\n", ah, mIsActive ? "is executing" : "self notify set", value ? value : (CFTypeRef)CFSTR("(NULL)")); 1242 AttributeChanged(ah, value); 1243 } 1244 1245 if (mPushedback && CFArrayGetCount(mPushedback) && !mProcessingPushbacks) 1246 { 1247 Debug("will process pushbacks (%@) later\n", mPushedback); 1248 mProcessingPushbacks = TRUE; 1249 dispatch_async(mDispatchQueue, ^{ try_pushbacks(); }); 1250 } 1251 1252 return; 1253 } 1254 1255 1256 void Transform::AttributeChanged(CFStringRef name, CFTypeRef value) 1257 { 1258 } 1259 1260 void Transform::AttributeChanged(SecTransformAttributeRef ah, CFTypeRef value) 1261 { 1262 AttributeChanged(ah2ta(ah)->name, value); 1263 } 1264 1265 CFArrayRef Transform::GetAllAH() { 1266 CFIndex cnt = CFSetGetCount(mAttributes); 1267 const void **values = (const void **)alloca(sizeof(void*)*cnt); 1268 CFSetGetValues(mAttributes, values); 1269 return CFArrayCreate(NULL, values, cnt, &kCFTypeArrayCallBacks); 1270 } 1271 1272 CFTypeRef Transform::Execute(dispatch_queue_t deliveryQueue, SecMessageBlock deliveryBlock, CFErrorRef* errorRef) 1273 { 1274 if (!mGroup) 1275 { 1276 CFTypeRef g = GroupTransform::Make(); 1277 mGroup = (GroupTransform*)CoreFoundationHolder::ObjectFromCFType(g); 1278 mGroup->AddMemberToGroup(this->GetCFObject()); 1279 SecMessageBlock smb = ^(CFTypeRef message, CFErrorRef error, Boolean isFinal) 1280 { 1281 deliveryBlock(message, error, isFinal); 1282 if (isFinal) 1283 { 1284 dispatch_async(this->mDispatchQueue, ^{ 1285 CFReleaseSafe(g); 1286 }); 1287 } 1288 }; 1289 1290 CFTypeRef ret = this->Execute(deliveryQueue, deliveryBlock ? smb : (SecMessageBlock) NULL, errorRef); 1291 1292 if (!deliveryBlock) 1293 { 1294 CFReleaseNull(g); 1295 } 1296 1297 return ret; 1298 } 1299 1300 if (mIsActive) 1301 { 1302 if (errorRef) 1303 { 1304 *errorRef = CreateSecTransformErrorRef(kSecTransformTransformIsExecuting, "The %@ transform has already executed, it may not be executed again.", GetName()); 1305 } 1306 1307 return NULL; 1308 } 1309 1310 // Do a retain on our parent since we are using it 1311 GroupTransform *rootGroup = GetRootGroup(); 1312 CFRetainSafe(rootGroup->GetCFObject()); 1313 1314 CFTypeRef result = NULL; 1315 1316 CFTypeRef monitorRef = BlockMonitor::Make(deliveryQueue, deliveryBlock); 1317 1318 __block CFStringRef outputAttached = NULL; 1319 1320 dispatch_queue_t p2 = dispatch_queue_create("activate phase2", NULL); 1321 dispatch_queue_t p3 = dispatch_queue_create("activate phase3", NULL); 1322 dispatch_suspend(p2); 1323 dispatch_suspend(p3); 1324 // walk the transform, doing phase1 activating as we go, and queueing phase2 and phase3 work 1325 CFErrorRef temp = TraverseTransform(NULL, ^(Transform *t){ 1326 return t->ExecuteOperation(outputAttached, (SecMonitorRef)monitorRef, p2, p3); 1327 }); 1328 // ExecuteOperation is not called for the outer group, so we need to manually set mISActive for it. 1329 rootGroup->mIsActive = true; 1330 rootGroup->StartingExecutionInGroup(); 1331 dispatch_resume(p2); 1332 dispatch_sync(p2, ^{ dispatch_resume(p3); }); 1333 dispatch_sync(p3, ^{ dispatch_release(p2); }); 1334 dispatch_release(p3); 1335 1336 if (errorRef) 1337 { 1338 *errorRef = temp; 1339 } 1340 if (temp) { 1341 // It is safe to keep the monitors attached, because it is invalid to try to execute again, BUT 1342 // we do need to release the reference to the group that the monitor would normally release 1343 // when it processes the final message. 1344 CFReleaseSafe(rootGroup->GetCFObject()); 1345 CFReleaseNull(monitorRef); 1346 rootGroup->StartedExecutionInGroup(false); 1347 return NULL; 1348 } 1349 1350 dispatch_group_t initialized = dispatch_group_create(); 1351 rootGroup->ForAllNodesAsync(true, initialized, ^(Transform*t) { 1352 t->Initialize(); 1353 }); 1354 1355 dispatch_group_notify(initialized, rootGroup->mDispatchQueue, ^{ 1356 dispatch_release(initialized); 1357 dispatch_group_t activated = dispatch_group_create(); 1358 dispatch_group_enter(activated); 1359 dispatch_async(rootGroup->mDispatchQueue, ^{ 1360 rootGroup->ForAllNodesAsync(true, activated, ^(Transform*t) { 1361 t->ActivateInputs(); 1362 }); 1363 dispatch_group_leave(activated); 1364 }); 1365 dispatch_group_notify(activated, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 1366 dispatch_release(activated); 1367 // once we have been activated (but not before!), the monitor belongs to the group, and we can drop our claim 1368 CFReleaseSafe(monitorRef); 1369 rootGroup->StartedExecutionInGroup(true); 1370 }); 1371 }); 1372 1373 return result; 1374 } 1375 1376 1377 void Transform::Initialize() 1378 { 1379 } 1380 1381 static void ActivateInputs_set(const void *v, void *unused) { 1382 transform_attribute *ta = static_cast<transform_attribute *>(ah2ta(const_cast<void *>(v))); 1383 if (ta->value && internalID == CFGetTypeID(ta->value)) { 1384 Source* s = (Source*) CoreFoundationHolder::ObjectFromCFType(ta->value); 1385 s->Activate(); 1386 } 1387 } 1388 1389 void Transform::ActivateInputs() 1390 { 1391 (void)transforms_assume_zero(mIsActive && this != dispatch_get_specific(&dispatchQueueToTransformKey)); 1392 1393 // now run all of the forward links 1394 if (!mIsFinalizing) { 1395 CFSetApplyFunction(mAttributes, ActivateInputs_set, NULL); 1396 } 1397 } 1398 1399 CFErrorRef Transform::ForAllNodes(bool parallel, bool includeOwningGroup, Transform::TransformOperation op) 1400 { 1401 GroupTransform *g = GetRootGroup(); 1402 if (g) { 1403 return g->ForAllNodes(parallel, includeOwningGroup, op); 1404 } else { 1405 return op(this); 1406 } 1407 } 1408 1409 CFErrorRef Transform::TraverseTransform(CFMutableSetRef visited, TransformOperation t) 1410 { 1411 return ForAllNodes(true, true, t); 1412 } 1413 1414 CFErrorRef Transform::ExecuteOperation(CFStringRef &outputAttached, SecMonitorRef output, dispatch_queue_t phase2, dispatch_queue_t phase3) 1415 { 1416 if (!mGroup) { 1417 // top level groups are special, and don't go through this path. 1418 return NULL; 1419 } 1420 1421 if (!TransformCanExecute()) 1422 { 1423 // oops, this transform isn't ready to go 1424 return CreateSecTransformErrorRef(kSecTransformErrorNotInitializedCorrectly, "The transform %@ was not ready for execution.", GetName()); 1425 } 1426 1427 // check to see if required attributes are connected or set 1428 CFIndex i, numAttributes = CFSetGetCount(mAttributes); 1429 transform_attribute **attributes = (transform_attribute **)alloca(numAttributes * sizeof(transform_attribute *)); 1430 TAGetAll(attributes); 1431 CFMutableArrayRef still_need = NULL; 1432 for(i = 0; i < numAttributes; ++i) { 1433 transform_attribute *ta = attributes[i]; 1434 if (ta->required && ta->value == NULL && !ta->has_incoming_connection) { 1435 if (!still_need) { 1436 still_need = CFArrayCreateMutable(NULL, i, &kCFTypeArrayCallBacks); 1437 } 1438 CFArrayAppendValue(still_need, ta->name); 1439 } 1440 } 1441 if (still_need) { 1442 CFStringRef elist = CFStringCreateByCombiningStrings(NULL, still_need, CFSTR(", ")); 1443 CFErrorRef err = CreateSecTransformErrorRef(kSecTransformErrorMissingParameter, "Can not execute %@, missing required attributes: %@", GetName(), elist); 1444 CFReleaseNull(elist); 1445 CFReleaseNull(still_need); 1446 return err; 1447 } 1448 1449 // see if we can attach our output here (note mAttributes may have changed) 1450 numAttributes = CFSetGetCount(mAttributes); 1451 attributes = (transform_attribute **)alloca(numAttributes * sizeof(transform_attribute *)); 1452 TAGetAll(attributes); 1453 for (i = 0; i < numAttributes; ++i) 1454 { 1455 transform_attribute *ta = attributes[i]; 1456 CFIndex arraySize = ta->connections ? CFArrayGetCount(ta->connections) : 0; 1457 if (arraySize == 0 && ta->requires_outbound_connection) 1458 { 1459 if (CFStringCompare(ta->name, kSecTransformOutputAttributeName, 0) == kCFCompareEqualTo) { 1460 // this is a place where we can hook up our output -- maybe 1461 if (outputAttached) 1462 { 1463 // oops, we've already done that. 1464 return CreateSecTransformErrorRef(kSecTransformErrorMoreThanOneOutput, "Both %@ and %@ have loose outputs, attach one to something", outputAttached, ta->transform->GetName()); 1465 } 1466 // Delay the connect until after ForAllNodes returns 1467 dispatch_async(phase2, ^{ 1468 SecTransformConnectTransformsInternal(mGroup->GetCFObject(), 1469 GetCFObject(), kSecTransformOutputAttributeName, 1470 output, kSecTransformInputAttributeName); 1471 }); 1472 outputAttached = ta->transform->GetName(); 1473 1474 // activate the attached monitor 1475 Monitor* m = (Monitor*) CoreFoundationHolder::ObjectFromCFType(output); 1476 m->mIsActive = true; 1477 1478 // add the monitor to the output so that it doesn't get activated twice 1479 } else { 1480 return CreateSecTransformErrorRef(kSecTransformErrorNotInitializedCorrectly, "Attribute %@ (in %@) requires an outbound connection and doesn't have one", ta->name, GetName()); 1481 } 1482 1483 break; 1484 } 1485 } 1486 1487 // Delay activation until after the Monitor is connected 1488 dispatch_async(phase3, ^{ 1489 phase3Activation(); 1490 }); 1491 1492 return NULL; 1493 } 1494 1495 1496 1497 void Transform::DoPhase3Activation() 1498 { 1499 this->mIsActive = true; 1500 // execution has now truly started ("mIsActive is true") 1501 CFErrorRef initError = TransformStartingExecution(); 1502 if (initError) 1503 { 1504 // Oops, now execution is about to grind to a halt 1505 this->SendAttribute(AbortAH, initError); 1506 } 1507 1508 dispatch_resume(this->mActivationQueue); 1509 dispatch_group_async(this->mActivationPending, this->mActivationQueue, ^{ 1510 dispatch_release(this->mActivationQueue); 1511 this->mActivationQueue = NULL; 1512 }); 1513 } 1514 1515 1516 1517 // This would be best expressed as a block, but we seem to run into compiler errors 1518 void Transform::phase3Activation() 1519 { 1520 dispatch_async(this->mDispatchQueue, ^ 1521 { 1522 DoPhase3Activation(); 1523 }); 1524 } 1525 1526 1527 Boolean Transform::TransformCanExecute() 1528 { 1529 return true; 1530 } 1531 1532 1533 1534 CFErrorRef Transform::TransformStartingExecution() 1535 { 1536 return NULL; 1537 } 1538 1539 1540 1541 bool Transform::IsExternalizable() 1542 { 1543 return true; 1544 } 1545 1546 static const void *CFTypeOrNULLRetain(CFAllocatorRef allocator, const void *value) { 1547 return CFRetainSafe(value); 1548 } 1549 1550 static void CFTypeOrNULLRelease(CFAllocatorRef allocator, const void *value) { 1551 CFReleaseNull(value); 1552 } 1553 1554 static CFStringRef CFTypeOrNULLCopyDescription (const void *value) { 1555 if (value != NULL) { 1556 return CFCopyDescription(value); 1557 } else { 1558 return CFSTR("NULL"); 1559 } 1560 } 1561 1562 static Boolean CFTypeOrNULLEqual(const void *value1, const void *value2) { 1563 if (value1 == NULL && value2 == NULL) { 1564 return TRUE; 1565 } else { 1566 if (value1 == NULL || value2 == NULL) { 1567 return FALSE; 1568 } else { 1569 return CFEqual(value1, value2); 1570 } 1571 } 1572 } 1573 1574 // Returns a dictionary of all the meta attributes that will need to be reset on a RestoreState 1575 CFDictionaryRef Transform::GetAHDictForSaveState(SecTransformStringOrAttributeRef key) 1576 { 1577 SecTransformMetaAttributeType types[] = 1578 { 1579 kSecTransformMetaAttributeRequired, 1580 kSecTransformMetaAttributeRequiresOutboundConnection, 1581 kSecTransformMetaAttributeDeferred, 1582 kSecTransformMetaAttributeStream, 1583 kSecTransformMetaAttributeCanCycle, 1584 kSecTransformMetaAttributeValue 1585 }; 1586 1587 CFIndex i, cnt = sizeof(types)/sizeof(SecTransformMetaAttributeType); 1588 CFTypeRef values[cnt]; 1589 CFNumberRef keys[cnt]; 1590 key = getAH(key); 1591 1592 // NOTE: we save meta attributes that are in their "default" state on purpose because the 1593 // default may change in the future and we definitely want to restore the default values at 1594 // time of save (i.e. if "stream=1" is the 10.7 default, but "stream=0" becomes the 10.8 1595 // default we want to load all old transforms with stream=1, the simplest way to do that is 1596 // to store all values, not just non-default values) 1597 for(i = 0; i < cnt; ++i) 1598 { 1599 values[i] = GetMetaAttribute(key, types[i]); 1600 int tmp = (int)types[i]; 1601 keys[i] = CFNumberCreate(NULL, kCFNumberIntType, &tmp); 1602 } 1603 1604 static CFDictionaryValueCallBacks CFTypeOrNULL; 1605 static dispatch_once_t once; 1606 dispatch_block_t b = 1607 ^{ 1608 CFTypeOrNULL.version = 0; 1609 CFTypeOrNULL.retain = CFTypeOrNULLRetain; 1610 CFTypeOrNULL.release = CFTypeOrNULLRelease; 1611 CFTypeOrNULL.copyDescription = CFTypeOrNULLCopyDescription; 1612 CFTypeOrNULL.equal = CFTypeOrNULLEqual; 1613 }; 1614 dispatch_once(&once, b); 1615 1616 CFDictionaryRef ret = CFDictionaryCreate(NULL, (const void**)&keys, (const void**)&values, cnt, &kCFTypeDictionaryKeyCallBacks, &CFTypeOrNULL); 1617 1618 for(i = 0; i < cnt; ++i) 1619 { 1620 CFReleaseNull(keys[i]); 1621 } 1622 1623 return ret; 1624 } 1625 1626 // return everything that doesn't have ignore_while_externalizing set 1627 CFDictionaryRef Transform::CopyState() 1628 { 1629 CFIndex i, j, cnt = CFSetGetCount(mAttributes); 1630 transform_attribute **attrs = (transform_attribute**)malloc(cnt*sizeof(transform_attribute*)); 1631 CFStringRef *names = (CFStringRef*)malloc(cnt*sizeof(CFStringRef)); 1632 CFDictionaryRef *values = (CFDictionaryRef*)malloc(sizeof(CFDictionaryRef) * cnt); 1633 1634 if (attrs == NULL || names == NULL || values == NULL) { 1635 free(attrs); 1636 free(names); 1637 free(values); 1638 return NULL; 1639 } 1640 1641 TAGetAll(attrs); 1642 for(i = j = 0; i < cnt; ++i) 1643 { 1644 transform_attribute *ta = attrs[i]; 1645 if (!ta->ignore_while_externalizing) 1646 { 1647 names[j] = ta->name; 1648 values[j++] = GetAHDictForSaveState(ta->name); 1649 } 1650 } 1651 1652 CFDictionaryRef result = CFDictionaryCreate(NULL, (const void**)&names, (const void**)&values, j, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); 1653 1654 free(names); 1655 1656 for(i = j = 0; i < cnt; ++i) 1657 { 1658 transform_attribute *ta = attrs[i]; 1659 if (!ta->ignore_while_externalizing) 1660 { 1661 CFReleaseNull(values[j++]); 1662 } 1663 } 1664 1665 free(attrs); 1666 free(values); 1667 return result; 1668 } 1669 1670 1671 1672 void Transform::RestoreState(CFDictionaryRef state) 1673 { 1674 CFIndex i, cnt = CFDictionaryGetCount(state); 1675 const void 1676 **keys = (const void **)alloca(sizeof(void*)*cnt), 1677 **values = (const void **)alloca(sizeof(void*)*cnt); 1678 1679 CFDictionaryGetKeysAndValues(state, keys, values); 1680 1681 // Open issue -- do we need to do anything to values that are already set, but are not in "state"? 1682 // this isn't an issue right now, which is only used on the SecTransformCopyExternalRepresentation path which starts with brand new objects, 1683 // it only becomes an issue if we add a ResetFromState, or use it internally in that role. 1684 1685 for(i = 0; i < cnt; i++) 1686 { 1687 SecTransformAttributeRef ah = getAH(keys[i]); 1688 1689 if (NULL == ah) 1690 { 1691 continue; 1692 } 1693 1694 CFIndex j, meta_cnt = CFDictionaryGetCount((CFDictionaryRef)values[i]); 1695 const void **types = (const void**)alloca(sizeof(void*)*meta_cnt), **meta_values = (const void**)alloca(sizeof(void*)*meta_cnt); 1696 CFDictionaryGetKeysAndValues((CFDictionaryRef)values[i], types, meta_values); 1697 1698 int t; 1699 for(j = 0; j < meta_cnt; ++j) 1700 { 1701 CFNumberGetValue((CFNumberRef)types[j], kCFNumberIntType, &t); 1702 if (t == kSecTransformMetaAttributeValue) 1703 { 1704 if (meta_values[j]) { 1705 // SendMetaAttribute doesn't activate the callbacks 1706 SetAttribute(ah, meta_values[j]); 1707 } 1708 } 1709 else 1710 { 1711 CFErrorRef result = SendMetaAttribute(ah, (SecTransformMetaAttributeType)t, meta_values[j]); 1712 CFReleaseNull(result); // see <rdar://problem/8741628> Transform::RestoreState is ignoring error returns 1713 } 1714 } 1715 1716 CFErrorRef result = SendMetaAttribute(ah, kSecTransformMetaAttributeExternalize, kCFBooleanTrue); 1717 CFReleaseNull(result); // see <rdar://problem/8741628> Transform::RestoreState is ignoring error returns 1718 } 1719 } 1720 1721 GroupTransform* Transform::GetRootGroup() 1722 { 1723 GroupTransform *g = mGroup; 1724 if (g) { 1725 while (g->mGroup) { 1726 g = g->mGroup; 1727 } 1728 } else { 1729 if (CFGetTypeID(this->GetCFObject()) == SecGroupTransformGetTypeID()) { 1730 return (GroupTransform *)this; 1731 } 1732 } 1733 return g; 1734 } 1735 1736 CFDictionaryRef Transform::GetCustomExternalData() 1737 { 1738 return NULL; 1739 } 1740 1741 void Transform::SetCustomExternalData(CFDictionaryRef customData) 1742 { 1743 return; 1744 } 1745 1746 CFDictionaryRef Transform::Externalize(CFErrorRef* error) 1747 { 1748 if (mIsActive) 1749 { 1750 return (CFDictionaryRef)CreateSecTransformErrorRef(kSecTransformTransformIsExecuting, "The %@ transform is executing, you need to externalize it prior to execution", GetName()); 1751 } 1752 1753 // make arrays to hold the transforms and the connections 1754 __block CFMutableArrayRef transforms = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); 1755 __block CFMutableArrayRef connections = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); 1756 GroupTransform *root = GetRootGroup(); 1757 1758 CFErrorRef err = ForAllNodes(false, true, ^(Transform *t) { 1759 if (t != root) { 1760 return t->ProcessExternalize(transforms, connections); 1761 } 1762 return (CFErrorRef)NULL; 1763 }); 1764 1765 if (NULL != err) 1766 { 1767 // Really? This just seems like a bad idea 1768 if (NULL != error) 1769 { 1770 CFRetainSafe(err); 1771 *error = err; 1772 } 1773 return NULL; 1774 1775 } 1776 1777 // make a dictionary to hold the output 1778 CFMutableDictionaryRef output = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); 1779 CFDictionaryAddValue(output, EXTERN_TRANSFORM_TRANSFORM_ARRAY, transforms); 1780 CFDictionaryAddValue(output, EXTERN_TRANSFORM_CONNECTION_ARRAY, connections); 1781 1782 // clean up 1783 CFReleaseNull(connections); 1784 CFReleaseNull(transforms); 1785 1786 return output; 1787 } 1788 1789 CFErrorRef Transform::ProcessExternalize(CFMutableArrayRef transforms, CFMutableArrayRef connections) 1790 { 1791 if (!IsExternalizable()) { 1792 return NULL; 1793 } 1794 1795 CFDictionaryRef state = CopyState(); 1796 if (state && CFGetTypeID(state) == CFErrorGetTypeID()) { 1797 return (CFErrorRef)state; 1798 } 1799 1800 // make a dictionary to hold the name, type, and state of this node 1801 CFMutableDictionaryRef node = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); 1802 CFDictionaryAddValue(node, EXTERN_TRANSFORM_NAME, GetName()); 1803 1804 CFTypeRef type = CFStringCreateCopy(NULL, mTypeName); 1805 CFDictionaryAddValue(node, EXTERN_TRANSFORM_TYPE, type); 1806 CFReleaseNull(type); 1807 1808 if (state != NULL) 1809 { 1810 CFDictionaryAddValue(node, EXTERN_TRANSFORM_STATE, state); 1811 CFReleaseNull(state); 1812 } 1813 1814 CFDictionaryRef customItems = GetCustomExternalData(); 1815 if (NULL != customItems) 1816 { 1817 CFDictionaryAddValue(node, EXTERN_TRANSFORM_CUSTOM_EXPORTS_DICTIONARY, customItems); 1818 CFReleaseNull(customItems); 1819 } 1820 1821 // append the resulting dictionary to the node list 1822 CFArrayAppendValue(transforms, node); 1823 CFReleaseNull(node); 1824 1825 // now walk the attribute list 1826 CFIndex numAttributes = CFSetGetCount(mAttributes); 1827 transform_attribute **attributes = (transform_attribute**)malloc(numAttributes*sizeof(transform_attribute*)); 1828 1829 if (attributes == NULL) { 1830 return GetNoMemoryErrorAndRetain(); 1831 } 1832 1833 TAGetAll(attributes); 1834 1835 CFIndex i; 1836 1837 // walk the forward links 1838 for (i = 0; i < numAttributes; ++i) 1839 { 1840 CFIndex arraySize = attributes[i]->connections ? CFArrayGetCount(attributes[i]->connections) : 0; 1841 if (arraySize != 0) 1842 { 1843 CFIndex j; 1844 for (j = 0; j < arraySize; ++j) 1845 { 1846 transform_attribute *ta = ah2ta((SecTransformAttributeRef)CFArrayGetValueAtIndex(attributes[i]->connections, j)); 1847 1848 if (!ta->transform->IsExternalizable()) { 1849 // just pretend non-externalizable transforms don't even exist. Don't write out connections, and don't talk to them about externalizing. 1850 continue; 1851 } 1852 1853 // add this forward connection to the array -- make a dictionary 1854 CFMutableDictionaryRef connection = 1855 CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); 1856 1857 CFDictionaryAddValue(connection, EXTERN_TRANSFORM_FROM_NAME, GetName()); 1858 CFDictionaryAddValue(connection, EXTERN_TRANSFORM_FROM_ATTRIBUTE, attributes[i]->name); 1859 CFDictionaryAddValue(connection, EXTERN_TRANSFORM_TO_NAME, ta->transform->GetName()); 1860 CFDictionaryAddValue(connection, EXTERN_TRANSFORM_TO_ATTRIBUTE, ta->name); 1861 1862 CFArrayAppendValue(connections, connection); 1863 CFReleaseNull(connection); 1864 } 1865 } 1866 } 1867 1868 free(attributes); 1869 1870 return NULL; 1871 }