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