/ OSX / libsecurity_transform / lib / GroupTransform.cpp
GroupTransform.cpp
  1  #include "GroupTransform.h"
  2  #include "Utilities.h"
  3  #include "misc.h"
  4  #include <string>
  5  #include <libkern/OSAtomic.h>
  6  
  7  using namespace std;
  8  
  9  CFStringRef kSecGroupTransformType = CFSTR("GroupTransform");
 10  
 11  GroupTransform::GroupTransform() : Transform(kSecGroupTransformType, kSecGroupTransformType)
 12  {
 13  	mMembers = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
 14  	mAllChildrenFinalized = dispatch_group_create();
 15      mPendingStartupActivity = dispatch_group_create();
 16  }
 17  
 18  // Invoked by Transform::Finalize
 19  void GroupTransform::FinalizePhase2()
 20  {
 21  	// Any time afer mMembers is released this can be deleted, so we need a local.
 22  	CFArrayRef members = this->mMembers;
 23      dispatch_group_notify(mPendingStartupActivity, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
 24          if (mMembers) {
 25              this->mMembers = NULL;
 26              CFReleaseSafe(members);
 27          }
 28      });
 29      
 30  	// Delay the final delete of the group until all children are gone (and thus unable to die while referencing us).
 31  	dispatch_group_notify(mAllChildrenFinalized, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
 32  		delete this;
 33  	});
 34  }
 35  
 36  void GroupTransform::StartingExecutionInGroup()
 37  {
 38      this->mIsActive = true;
 39      dispatch_group_enter(mPendingStartupActivity);
 40      dispatch_group_enter(mAllChildrenFinalized);
 41  }
 42  
 43  void GroupTransform::StartedExecutionInGroup(bool succesful)
 44  {
 45      dispatch_group_leave(mPendingStartupActivity);
 46      dispatch_group_leave(mAllChildrenFinalized);
 47  }
 48  
 49  bool GroupTransform::validConnectionPoint(CFStringRef attributeName)
 50  {
 51      // We don't want connections to/from unexported attributes
 52      return NULL != this->getAH(attributeName, false);
 53  }
 54  
 55  GroupTransform::~GroupTransform()
 56  {
 57  	// mMembers already released (via FinalizePhase2)
 58  	dispatch_release(mAllChildrenFinalized);
 59      dispatch_release(mPendingStartupActivity);
 60  }
 61  
 62  void GroupTransform::ChildStartedFinalization(Transform *child)
 63  {
 64      // child started finalizing before the group??  Likely client over release.
 65      (void)transforms_assume(this->mIsFinalizing);
 66  	dispatch_group_leave(mAllChildrenFinalized);
 67  }
 68  
 69  CFTypeRef GroupTransform::Make()
 70  {
 71  	return CoreFoundationHolder::MakeHolder(kSecGroupTransformType, new GroupTransform());
 72  }
 73  
 74  static CFComparisonResult tr_cmp(const void *val1, const void *val2, void *context)
 75  {
 76      return (((intptr_t) val1 == (intptr_t) val2) ? kCFCompareEqualTo
 77            : ((intptr_t) val1 >  (intptr_t) val2) ? kCFCompareGreaterThan
 78            :                                        kCFCompareLessThan);
 79  }
 80  
 81  bool GroupTransform::HasMember(SecTransformRef member)
 82  {
 83  	// find the transform in the group
 84  	CFIndex numMembers = CFArrayGetCount(mMembers);
 85  	CFIndex i;
 86  	
 87  	for (i = 0; i < numMembers; ++i)
 88  	{
 89  		if (CFArrayGetValueAtIndex(mMembers, i) == member)
 90  		{
 91  			return true;
 92  		}
 93  	}
 94  	
 95  	return false;
 96  }
 97  
 98  
 99  	
100  void GroupTransform::RemoveMemberFromGroup(SecTransformRef member)
101  {
102  	// find the transform in the group and remove it
103  	// XXX: this would be a lot faster if it used CFArrayBSearchValues
104  	CFIndex numMembers = CFArrayGetCount(mMembers);
105  	CFIndex i;
106  	
107  	for (i = 0; i < numMembers; ++i)
108  	{
109  		if (CFArrayGetValueAtIndex(mMembers, i) == member)
110  		{
111  			// removing the item will release it, so we don't need an explicit release
112  			CFArrayRemoveValueAtIndex(mMembers, i);
113  			numMembers = CFArrayGetCount(mMembers);
114  			dispatch_group_leave(mAllChildrenFinalized);
115  		}
116  	}
117  }
118  
119  void GroupTransform::AddAllChildrenFinalizedCallback(dispatch_queue_t run_on, dispatch_block_t callback)
120  {
121  	dispatch_group_notify(mAllChildrenFinalized, run_on, callback);
122  }
123  
124  
125  void GroupTransform::AddMemberToGroup(SecTransformRef member)
126  {
127  	// set a backlink to this group in the child (used for abort and other purposes)
128  	Transform* transform = (Transform*) CoreFoundationHolder::ObjectFromCFType(member);
129  	// XXX: it seems like we should be able to ensure that we are the only caller to SetGroup, and that if the group is set to us we could skip this search... 
130      transform->SetGroup(this);
131      if (transform == this) {
132          // We don't want to be in our own membership list, at a minimum
133          // that makes reference counts cranky.
134          return;
135      }
136  	
137  	// check to make sure that member is not already in the group (the bsearch code is a bit more complex, but cuts run time for the 8163542 test from about 40 minutes to under a minute)
138  	CFIndex numMembers = CFArrayGetCount(mMembers);
139  	CFRange range = {0, numMembers};
140  	CFIndex at = CFArrayBSearchValues(mMembers, range, member, tr_cmp, NULL);
141  	SecTransformRef candiate = (at < numMembers) ? CFArrayGetValueAtIndex(mMembers, at) : NULL;
142  	if (member == candiate) {
143  		return;
144  	}
145  	
146  	CFArrayInsertValueAtIndex(mMembers, at, member);
147  	dispatch_group_enter(mAllChildrenFinalized);
148  }
149  
150  
151  
152  std::string GroupTransform::DebugDescription()
153  {
154  	return Transform::DebugDescription() + ": GroupTransform";
155  }
156  
157  
158  
159  class GroupTransformFactory : public TransformFactory
160  {
161  public:
162  	GroupTransformFactory();
163  	
164  	virtual CFTypeRef Make();
165  };
166  
167  
168  
169  TransformFactory* GroupTransform::MakeTransformFactory()
170  {
171  	return new GroupTransformFactory();
172  }
173  
174  
175  
176  GroupTransformFactory::GroupTransformFactory() : TransformFactory(kSecGroupTransformType)
177  {
178  }
179  
180  
181  
182  CFTypeRef GroupTransformFactory::Make()
183  {
184  	return GroupTransform::Make();
185  }
186  
187  
188  
189  CFTypeID GroupTransform::GetCFTypeID()
190  {
191  	return CoreFoundationObject::FindObjectType(kSecGroupTransformType);
192  }
193  
194  
195  
196  
197  SecTransformRef GroupTransform::FindFirstTransform()
198  {
199  	// look for a transform that has no connections to INPUT (prefer ones where INPUT is required)
200  	CFRange range;
201  	range.location = 0;
202  	range.length = CFArrayGetCount(mMembers);
203  	SecTransformRef items[range.length];
204  	SecTransformRef maybe = NULL;
205  	
206  	CFArrayGetValues(mMembers, range, items);
207  	
208  	CFIndex i;
209  	for (i = 0; i < range.length; ++i)
210  	{
211  		SecTransformRef tr = (SecTransformRef) items[i];
212  		Transform* t = (Transform*) CoreFoundationHolder::ObjectFromCFType(tr);
213  		SecTransformAttributeRef in = getAH(kSecTransformInputAttributeName, false);
214  		if (!t->GetMetaAttribute(in, kSecTransformMetaAttributeHasInboundConnection)) {
215  			maybe = tr;
216  			if (t->GetMetaAttribute(in, kSecTransformMetaAttributeRequired)) {
217  				return tr;
218  			}
219  		}
220  	}
221  	
222  	return maybe;
223  }
224  
225  
226  SecTransformRef GroupTransform::GetAnyMember()
227  {
228  	if (CFArrayGetCount(mMembers)) {
229  		return CFArrayGetValueAtIndex(mMembers, 0);
230  	} else {
231  		return NULL;
232  	}
233  }
234  
235  // Pretty horrible kludge -- we will do Very Bad Things if someone names their transform <anything>Monitor
236  SecTransformRef GroupTransform::FindMonitor()
237  {
238  	// list all transforms in the group
239  	CFRange range;
240  	range.location = 0;
241  	range.length = CFArrayGetCount(mMembers);
242  	SecTransformRef items[range.length];
243  	CFArrayGetValues(mMembers, range, items);
244  	
245  	// check each item to see if it is a monitor
246  	CFIndex i;
247  	for (i = 0; i < range.length; ++i)
248  	{
249  		SecTransformRef tr = (SecTransformRef) items[i];
250  		Transform* t = (Transform*) CoreFoundationHolder::ObjectFromCFType(tr);
251  		
252  		if (CFStringHasSuffix(t->mTypeName, CFSTR("Monitor"))) {
253  			return tr;
254  		}
255  	}
256  	
257  	return NULL;
258  }
259  
260  
261  
262  SecTransformRef GroupTransform::FindLastTransform()
263  {
264  	// If there's a monitor attached to this transform, the last transform is
265  	// the transform that points to it.  Otherwise, the last transform is the
266  	// one that has nothing connected to its output attribute and said attribute
267  	// is marked as requiring an outbound connection.
268  	
269  	// WARNING: if this function and Transform::ExecuteOperation disagree about
270  	// where to attach a monitor things could get funky.   It would be very nice
271  	// to implement one of these in terms of the other.
272  	
273  	SecTransformRef lastOrMonitor = FindMonitor(); // this will either be NULL or the monitor.
274  												   // We win either way.
275  	
276  	// list all transforms in the group
277  	CFRange range;
278  	range.location = 0;
279  	range.length = CFArrayGetCount(mMembers);
280  	SecTransformRef items[range.length];
281  	CFArrayGetValues(mMembers, range, items);
282  	
283  	// if the output attribute of a transform matches our target, we win
284  	CFIndex i;
285  	for (i = 0; i < range.length; ++i)
286  	{
287  		Transform* tr = (Transform*) CoreFoundationHolder::ObjectFromCFType(items[i]);
288  		
289  		// get the output attribute for the transform
290  		transform_attribute* ta = tr->getTA(kSecTransformOutputAttributeName, false);
291  		
292  		if (lastOrMonitor == NULL)
293  		{
294  			if (ta->requires_outbound_connection && (ta->connections == NULL || (CFArrayGetCount(ta->connections) == 0)))
295  			{
296  				// this a transform with an unattached OUTPUT with RequiresOutboundConnection true
297  				return items[i];
298  			}
299  		} else {
300  			if (ta->connections) {
301  				// get all of the connections for that attribute, and see if one of them is the monitor
302  				CFRange connectionRange;
303  				connectionRange.location = 0;
304  				connectionRange.length = CFArrayGetCount(ta->connections);
305  				SecTransformAttributeRef attributeHandles[connectionRange.length];
306  				CFArrayGetValues(ta->connections, connectionRange, attributeHandles);
307  				
308  				CFIndex j;
309  				for (j = 0; j < connectionRange.length; ++j)
310  				{
311  					transform_attribute* ta = ah2ta(attributeHandles[j]);
312  					if (ta->transform->GetCFObject() == lastOrMonitor)
313  					{
314  						return items[i];
315  					}
316  				}
317  			}
318  		}
319  	}
320  	
321  	// this chain is seriously whacked!!!
322  	return NULL;
323  }
324  
325  SecTransformRef GroupTransform::FindByName(CFStringRef name)
326  {
327  	__block SecTransformRef ret = NULL;
328  	static CFErrorRef early_return = CFErrorCreate(NULL, kCFErrorDomainPOSIX, EEXIST, NULL);
329  	
330  	ForAllNodes(true, true, ^(Transform *t){
331  		if (!CFStringCompare(name, t->GetName(), 0)) {
332  			ret = t->GetCFObject();
333  			return early_return;
334  		}
335  		return (CFErrorRef)NULL;
336  	});
337  	
338  	return ret;
339  }
340  
341  CFDictionaryRef GroupTransform::Externalize(CFErrorRef* error)
342  {	
343  	return NULL;
344  }
345  
346  // Visit all children once.   Unlike ForAllNodes there is no way to early exit, nor a way to return a status.
347  // Returns when all work is scheduled, use group to determine completion of work.
348  // See also ForAllNodes below.
349  void GroupTransform::ForAllNodesAsync(bool opExecutesOnGroups, dispatch_group_t group, Transform::TransformAsyncOperation op)
350  {
351      dispatch_group_enter(group);
352  	CFIndex lim = mMembers ? CFArrayGetCount(mMembers) : 0;
353  	
354  	if (opExecutesOnGroups)
355  	{
356  		dispatch_group_async(group, mDispatchQueue, ^{
357              op(this);
358          });
359  	}
360  	
361  	for(CFIndex i = 0; i < lim; ++i)
362  	{
363  		SecTransformRef tr = CFArrayGetValueAtIndex(mMembers, i);
364          Transform *t = (Transform*)CoreFoundationHolder::ObjectFromCFType(tr);
365          
366          if (CFGetTypeID(tr) == SecGroupTransformGetTypeID()) {
367              GroupTransform *g = (GroupTransform*)t;
368              g->ForAllNodesAsync(true, group, op);
369          } else {
370              dispatch_group_async(group, t->mDispatchQueue, ^{ 
371                  op(t);
372              });
373          }
374  	}
375      dispatch_group_leave(group);
376  }
377  
378  // Visit all nodes once (at most), attempts to stop if any op
379  // returns non-NULL (if parallel is true in flight ops are not
380  // stopped).   Returns when all work is complete, and returns
381  // the "first" non-NULL op value (or NULL if all ops returned
382  // NULL).  Uses ForAllNodes below to do the dirty work.
383  CFErrorRef GroupTransform::ForAllNodes(bool parallel, bool opExecutesOnGroups, Transform::TransformOperation op)
384  {
385      dispatch_group_t inner_group = dispatch_group_create();
386      
387      CFErrorRef err = NULL;
388      RecurseForAllNodes(inner_group, &err, parallel, opExecutesOnGroups, op);
389      
390      dispatch_group_wait(inner_group, DISPATCH_TIME_FOREVER);
391      dispatch_release(inner_group);
392      
393      return err;
394  }
395  
396  // Visit all children once (at most), because groups can appear in
397  // multiple other groups use visitedGroups (protected by
398  // rw_queue) to avoid multiple visits.   Will stop if an op
399  // returns non-NULL.
400  // (Used only by ForAllNodes above)
401  void GroupTransform::RecurseForAllNodes(dispatch_group_t group, CFErrorRef *err_, bool parallel, bool opExecutesOnGroups, Transform::TransformOperation op)
402  {
403      __block CFErrorRef *err = err_;
404      void (^set_error)(CFErrorRef new_err) = ^(CFErrorRef new_err) {
405          if (new_err) {
406  	    if (!OSAtomicCompareAndSwapPtrBarrier(NULL, (void *)new_err, (void**)err)) {
407                  CFReleaseNull(new_err);
408              }
409          }
410      };
411      void (^runOp)(Transform *t) = ^(Transform *t){
412          if (parallel) {
413              dispatch_group_async(group, t->mDispatchQueue, ^{ 
414                  set_error(op(t));
415              });
416          } else {
417              set_error(op(t));
418          }
419      };
420  
421      dispatch_group_enter(group);
422  	if (opExecutesOnGroups) {
423          runOp(this);
424  	}
425  	
426      
427  	CFIndex i, lim = CFArrayGetCount(mMembers);
428  	
429  	for(i = 0; i < lim && !*err; ++i) {
430  		SecTransformRef tr = CFArrayGetValueAtIndex(mMembers, i);
431  		Transform *t = (Transform*)CoreFoundationHolder::ObjectFromCFType(tr);
432          
433          if (CFGetTypeID(tr) == SecGroupTransformGetTypeID()) {
434              GroupTransform *g = (GroupTransform*)t;
435              g->RecurseForAllNodes(group, err, parallel, opExecutesOnGroups, op);
436          } else {
437              runOp(t);
438          }
439  	}
440  
441      dispatch_group_leave(group);
442  }
443  
444  // Return a dot (GraphViz) description of the group.
445  // For debugging use.   Exact content and style may
446  // change.   Currently all transforms and attributes
447  // are displayed, but only string values are shown
448  // (and no meta attributes are indicated).
449  CFStringRef GroupTransform::DotForDebugging()
450  {
451      __block CFMutableStringRef result = CFStringCreateMutable(NULL, 0);
452      CFStringAppend(result, CFSTR("digraph \"G\" {\n"));
453      
454      dispatch_queue_t collect_nodes = dispatch_queue_create("dot-node-collector", NULL);
455      dispatch_group_t complete_nodes = dispatch_group_create();
456      dispatch_queue_t collect_connections = dispatch_queue_create("dot-connection-collector", NULL);
457      dispatch_group_t complete_connections = dispatch_group_create();
458      // Before we reference a node we need it to be declared in the correct
459      // graph cluster, so we defer all the connection output until we
460      // have all the nodes defined.
461      dispatch_suspend(collect_connections);
462      
463      
464      this->ForAllNodesAsync(true, complete_nodes, ^(Transform *t) {
465          CFStringRef name = t->GetName();
466          __block CFMutableStringRef group_nodes_out = CFStringCreateMutable(NULL, 0);
467          __block CFMutableStringRef group_connections_out = CFStringCreateMutable(NULL, 0);
468          CFStringRef line_out = CFStringCreateWithFormat(NULL, NULL, CFSTR("\tsubgraph \"cluster_%@\" {\n"), name);
469          CFStringAppend(group_nodes_out, line_out);
470          CFReleaseNull(line_out);
471          line_out = NULL;
472          
473          CFIndex n_attributes = t->GetAttributeCount();
474          transform_attribute **attributes = (transform_attribute **)alloca(n_attributes * sizeof(transform_attribute *));
475          t->TAGetAll(attributes);
476          CFMutableArrayRef most_dot_names = CFArrayCreateMutable(NULL, n_attributes -1, &kCFTypeArrayCallBacks);
477          for(int i = 0; i < n_attributes; i++) {
478              CFStringRef label = attributes[i]->name;
479              if (attributes[i]->value) {
480                  if (CFGetTypeID(attributes[i]->value) == CFStringGetTypeID()) {
481                      label = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@=%@"), attributes[i]->name, attributes[i]->value);
482                  }
483              }
484              if (!label) {
485                  label = CFStringCreateCopy(NULL, attributes[i]->name);
486              }
487              
488              CFStringRef dot_node_name = CFStringCreateWithFormat(NULL, NULL, CFSTR("\"%@#%@\""), name, attributes[i]->name);
489              if (CFStringCompare(CFSTR("NAME"), label, 0)) {
490                  CFArrayAppendValue(most_dot_names, dot_node_name);
491              }
492              line_out = CFStringCreateWithFormat(NULL, NULL, CFSTR("\t\t%@ [shape=plaintext, label=\"%@\"]\n"), dot_node_name, label);
493              CFStringAppend(group_nodes_out, line_out);
494              CFReleaseNull(line_out);
495              line_out = NULL;
496              CFReleaseNull(label);
497              
498              CFIndex n_connections = attributes[i]->connections ? CFArrayGetCount(attributes[i]->connections) : 0;
499              for(int j = 0; j < n_connections; j++) {
500                  transform_attribute *connected_to = ah2ta(CFArrayGetValueAtIndex(attributes[i]->connections, j));
501                  line_out = CFStringCreateWithFormat(NULL, NULL, CFSTR("\t%@ -> \"%@#%@\"\n"), dot_node_name, connected_to->transform->GetName(), connected_to->name);
502                  CFStringAppend(group_connections_out, line_out);
503                  CFReleaseNull(line_out);
504              }
505              
506              CFSafeRelease(dot_node_name);
507          }
508          
509          CFStringRef combinedString = CFStringCreateByCombiningStrings(NULL, most_dot_names, CFSTR(" "));
510          line_out = CFStringCreateWithFormat(NULL, NULL, CFSTR("\t\t\"%@#NAME\" -> { %@ } [style=invis]\n\t}\n"), name, combinedString);
511          CFStringAppend(group_nodes_out, line_out);
512          CFReleaseNull(line_out);
513          CFSafeRelease(most_dot_names);
514          CFSafeRelease(combinedString);
515          if (t->mGroup) {
516              line_out = CFStringCreateWithFormat(NULL, NULL, CFSTR("\t\"%@#NAME\" -> \"%@#NAME\" [style=dotted,weight=5]\n"), name, t->mGroup->GetName());
517              CFStringAppend(group_connections_out, line_out);
518              CFReleaseNull(line_out);
519          }
520          line_out = NULL;
521          
522          dispatch_async(collect_nodes, ^(void) {
523              CFStringAppend(result, group_nodes_out);
524              CFReleaseNull(group_nodes_out);
525          });
526          dispatch_group_async(complete_connections, collect_connections, ^(void) {
527              // We don't really need to append to result on the collect_nodes queue
528              // because we happen to know no more work is going on on the collect_nodes
529              // queue, but if that ever changed we would have a hard to track down bug...
530              dispatch_async(collect_nodes, ^(void) {
531                  CFStringAppend(result, group_connections_out);
532                  CFReleaseNull(group_connections_out);
533              });
534          });
535      });
536      
537      dispatch_group_wait(complete_nodes, DISPATCH_TIME_FOREVER);
538      dispatch_release(complete_nodes);
539      dispatch_resume(collect_connections);
540      dispatch_release(collect_connections);
541      dispatch_group_wait(complete_connections, DISPATCH_TIME_FOREVER);
542      dispatch_release(complete_connections);
543      
544      dispatch_sync(collect_nodes, ^{
545          CFStringAppend(result, CFSTR("}\n"));
546      });
547      dispatch_release(collect_nodes);
548      return result;
549  }