--- /dev/null
+#include "GroupTransform.h"
+#include "Utilities.h"
+#include "misc.h"
+#include <string>
+#include <libkern/OSAtomic.h>
+
+using namespace std;
+
+CFStringRef kSecGroupTransformType = CFSTR("GroupTransform");
+
+GroupTransform::GroupTransform() : Transform(kSecGroupTransformType, kSecGroupTransformType)
+{
+ mMembers = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+ mAllChildrenFinalized = dispatch_group_create();
+ mPendingStartupActivity = dispatch_group_create();
+}
+
+// Invoked by Transform::Finalize
+void GroupTransform::FinalizePhase2()
+{
+ // Any time afer mMembers is released this can be deleted, so we need a local.
+ CFArrayRef members = this->mMembers;
+ dispatch_group_notify(mPendingStartupActivity, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
+ if (mMembers) {
+ this->mMembers = NULL;
+ CFRelease(members);
+ }
+ });
+
+ // Delay the final delete of the group until all children are gone (and thus unable to die while referencing us).
+ dispatch_group_notify(mAllChildrenFinalized, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
+ delete this;
+ });
+}
+
+void GroupTransform::StartingExecutionInGroup()
+{
+ this->mIsActive = true;
+ dispatch_group_enter(mPendingStartupActivity);
+ dispatch_group_enter(mAllChildrenFinalized);
+}
+
+void GroupTransform::StartedExecutionInGroup(bool succesful)
+{
+ dispatch_group_leave(mPendingStartupActivity);
+ dispatch_group_leave(mAllChildrenFinalized);
+}
+
+bool GroupTransform::validConnectionPoint(CFStringRef attributeName)
+{
+ // We don't want connections to/from unexported attributes
+ return NULL != this->getAH(attributeName, false);
+}
+
+GroupTransform::~GroupTransform()
+{
+ // mMembers already released (via FinalizePhase2)
+ dispatch_release(mAllChildrenFinalized);
+ dispatch_release(mPendingStartupActivity);
+}
+
+void GroupTransform::ChildStartedFinalization(Transform *child)
+{
+ // child started finalizing before the group?? Likely client over release.
+ (void)transforms_assume(this->mIsFinalizing);
+ dispatch_group_leave(mAllChildrenFinalized);
+}
+
+CFTypeRef GroupTransform::Make()
+{
+ return CoreFoundationHolder::MakeHolder(kSecGroupTransformType, new GroupTransform());
+}
+
+static CFComparisonResult tr_cmp(const void *val1, const void *val2, void *context)
+{
+ return (intptr_t)val1 - (intptr_t)val2;
+}
+
+bool GroupTransform::HasMember(SecTransformRef member)
+{
+ // find the transform in the group
+ CFIndex numMembers = CFArrayGetCount(mMembers);
+ CFIndex i;
+
+ for (i = 0; i < numMembers; ++i)
+ {
+ if (CFArrayGetValueAtIndex(mMembers, i) == member)
+ {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+
+
+void GroupTransform::RemoveMemberFromGroup(SecTransformRef member)
+{
+ // find the transform in the group and remove it
+ // XXX: this would be a lot faster if it used CFArrayBSearchValues
+ CFIndex numMembers = CFArrayGetCount(mMembers);
+ CFIndex i;
+
+ for (i = 0; i < numMembers; ++i)
+ {
+ if (CFArrayGetValueAtIndex(mMembers, i) == member)
+ {
+ // removing the item will release it, so we don't need an explicit release
+ CFArrayRemoveValueAtIndex(mMembers, i);
+ numMembers = CFArrayGetCount(mMembers);
+ dispatch_group_leave(mAllChildrenFinalized);
+ }
+ }
+}
+
+void GroupTransform::AddAllChildrenFinalizedCallback(dispatch_queue_t run_on, dispatch_block_t callback)
+{
+ dispatch_group_notify(mAllChildrenFinalized, run_on, callback);
+}
+
+
+void GroupTransform::AddMemberToGroup(SecTransformRef member)
+{
+ // set a backlink to this group in the child (used for abort and other purposes)
+ Transform* transform = (Transform*) CoreFoundationHolder::ObjectFromCFType(member);
+ // 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...
+ transform->SetGroup(this);
+ if (transform == this) {
+ // We don't want to be in our own membership list, at a minimum
+ // that makes reference counts cranky.
+ return;
+ }
+
+ // 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)
+ CFIndex numMembers = CFArrayGetCount(mMembers);
+ CFRange range = {0, numMembers};
+ CFIndex at = CFArrayBSearchValues(mMembers, range, member, tr_cmp, NULL);
+ SecTransformRef candiate = (at < numMembers) ? CFArrayGetValueAtIndex(mMembers, at) : NULL;
+ if (member == candiate) {
+ return;
+ }
+
+ CFArrayInsertValueAtIndex(mMembers, at, member);
+ dispatch_group_enter(mAllChildrenFinalized);
+}
+
+
+
+std::string GroupTransform::DebugDescription()
+{
+ return Transform::DebugDescription() + ": GroupTransform";
+}
+
+
+
+class GroupTransformFactory : public TransformFactory
+{
+public:
+ GroupTransformFactory();
+
+ virtual CFTypeRef Make();
+};
+
+
+
+TransformFactory* GroupTransform::MakeTransformFactory()
+{
+ return new GroupTransformFactory();
+}
+
+
+
+GroupTransformFactory::GroupTransformFactory() : TransformFactory(kSecGroupTransformType)
+{
+}
+
+
+
+CFTypeRef GroupTransformFactory::Make()
+{
+ return GroupTransform::Make();
+}
+
+
+
+CFTypeID GroupTransform::GetCFTypeID()
+{
+ return CoreFoundationObject::FindObjectType(kSecGroupTransformType);
+}
+
+
+
+
+SecTransformRef GroupTransform::FindFirstTransform()
+{
+ // look for a transform that has no connections to INPUT (prefer ones where INPUT is required)
+ CFRange range;
+ range.location = 0;
+ range.length = CFArrayGetCount(mMembers);
+ SecTransformRef items[range.length];
+ SecTransformRef maybe = NULL;
+
+ CFArrayGetValues(mMembers, range, items);
+
+ CFIndex i;
+ for (i = 0; i < range.length; ++i)
+ {
+ SecTransformRef tr = (SecTransformRef) items[i];
+ Transform* t = (Transform*) CoreFoundationHolder::ObjectFromCFType(tr);
+ SecTransformAttributeRef in = getAH(kSecTransformInputAttributeName, false);
+ if (!t->GetMetaAttribute(in, kSecTransformMetaAttributeHasInboundConnection)) {
+ maybe = tr;
+ if (t->GetMetaAttribute(in, kSecTransformMetaAttributeRequired)) {
+ return tr;
+ }
+ }
+ }
+
+ return maybe;
+}
+
+
+SecTransformRef GroupTransform::GetAnyMember()
+{
+ if (CFArrayGetCount(mMembers)) {
+ return CFArrayGetValueAtIndex(mMembers, 0);
+ } else {
+ return NULL;
+ }
+}
+
+// Pretty horrible kludge -- we will do Very Bad Things if someone names their transform <anything>Monitor
+SecTransformRef GroupTransform::FindMonitor()
+{
+ // list all transforms in the group
+ CFRange range;
+ range.location = 0;
+ range.length = CFArrayGetCount(mMembers);
+ SecTransformRef items[range.length];
+ CFArrayGetValues(mMembers, range, items);
+
+ // check each item to see if it is a monitor
+ CFIndex i;
+ for (i = 0; i < range.length; ++i)
+ {
+ SecTransformRef tr = (SecTransformRef) items[i];
+ Transform* t = (Transform*) CoreFoundationHolder::ObjectFromCFType(tr);
+
+ if (CFStringHasSuffix(t->mTypeName, CFSTR("Monitor"))) {
+ return tr;
+ }
+ }
+
+ return NULL;
+}
+
+
+
+SecTransformRef GroupTransform::FindLastTransform()
+{
+ // If there's a monitor attached to this transform, the last transform is
+ // the transform that points to it. Otherwise, the last transform is the
+ // one that has nothing connected to its output attribute and said attribute
+ // is marked as requiring an outbound connection.
+
+ // WARNING: if this function and Transform::ExecuteOperation disagree about
+ // where to attach a monitor things could get funky. It would be very nice
+ // to implement one of these in terms of the other.
+
+ SecTransformRef lastOrMonitor = FindMonitor(); // this will either be NULL or the monitor.
+ // We win either way.
+
+ // list all transforms in the group
+ CFRange range;
+ range.location = 0;
+ range.length = CFArrayGetCount(mMembers);
+ SecTransformRef items[range.length];
+ CFArrayGetValues(mMembers, range, items);
+
+ // if the output attribute of a transform matches our target, we win
+ CFIndex i;
+ for (i = 0; i < range.length; ++i)
+ {
+ Transform* tr = (Transform*) CoreFoundationHolder::ObjectFromCFType(items[i]);
+
+ // get the output attribute for the transform
+ transform_attribute* ta = tr->getTA(kSecTransformOutputAttributeName, false);
+
+ if (lastOrMonitor == NULL)
+ {
+ if (ta->requires_outbound_connection && (ta->connections == NULL || (CFArrayGetCount(ta->connections) == 0)))
+ {
+ // this a transform with an unattached OUTPUT with RequiresOutboundConnection true
+ return items[i];
+ }
+ } else {
+ if (ta->connections) {
+ // get all of the connections for that attribute, and see if one of them is the monitor
+ CFRange connectionRange;
+ connectionRange.location = 0;
+ connectionRange.length = CFArrayGetCount(ta->connections);
+ SecTransformAttributeRef attributeHandles[connectionRange.length];
+ CFArrayGetValues(ta->connections, connectionRange, attributeHandles);
+
+ CFIndex j;
+ for (j = 0; j < connectionRange.length; ++j)
+ {
+ transform_attribute* ta = ah2ta(attributeHandles[j]);
+ if (ta->transform->GetCFObject() == lastOrMonitor)
+ {
+ return items[i];
+ }
+ }
+ }
+ }
+ }
+
+ // this chain is seriously whacked!!!
+ return NULL;
+}
+
+SecTransformRef GroupTransform::FindByName(CFStringRef name)
+{
+ __block SecTransformRef ret = NULL;
+ static CFErrorRef early_return = CFErrorCreate(NULL, kCFErrorDomainPOSIX, EEXIST, NULL);
+
+ ForAllNodes(true, true, ^(Transform *t){
+ if (!CFStringCompare(name, t->GetName(), 0)) {
+ ret = t->GetCFObject();
+ return early_return;
+ }
+ return (CFErrorRef)NULL;
+ });
+
+ return ret;
+}
+
+CFDictionaryRef GroupTransform::Externalize(CFErrorRef* error)
+{
+ return NULL;
+}
+
+// Visit all children once. Unlike ForAllNodes there is no way to early exit, nor a way to return a status.
+// Returns when all work is scheduled, use group to determine completion of work.
+// See also ForAllNodes below.
+void GroupTransform::ForAllNodesAsync(bool opExecutesOnGroups, dispatch_group_t group, Transform::TransformAsyncOperation op)
+{
+ dispatch_group_enter(group);
+ CFIndex lim = mMembers ? CFArrayGetCount(mMembers) : 0;
+
+ if (opExecutesOnGroups)
+ {
+ dispatch_group_async(group, mDispatchQueue, ^{
+ op(this);
+ });
+ }
+
+ for(CFIndex i = 0; i < lim; ++i)
+ {
+ SecTransformRef tr = CFArrayGetValueAtIndex(mMembers, i);
+ Transform *t = (Transform*)CoreFoundationHolder::ObjectFromCFType(tr);
+
+ if (CFGetTypeID(tr) == SecGroupTransformGetTypeID()) {
+ GroupTransform *g = (GroupTransform*)t;
+ g->ForAllNodesAsync(true, group, op);
+ } else {
+ dispatch_group_async(group, t->mDispatchQueue, ^{
+ op(t);
+ });
+ }
+ }
+ dispatch_group_leave(group);
+}
+
+// Visit all nodes once (at most), attempts to stop if any op
+// returns non-NULL (if parallel is true in flight ops are not
+// stopped). Returns when all work is complete, and returns
+// the "first" non-NULL op value (or NULL if all ops returned
+// NULL). Uses ForAllNodes below to do the dirty work.
+CFErrorRef GroupTransform::ForAllNodes(bool parallel, bool opExecutesOnGroups, Transform::TransformOperation op)
+{
+ dispatch_group_t inner_group = dispatch_group_create();
+
+ CFErrorRef err = NULL;
+ RecurseForAllNodes(inner_group, &err, parallel, opExecutesOnGroups, op);
+
+ dispatch_group_wait(inner_group, DISPATCH_TIME_FOREVER);
+ dispatch_release(inner_group);
+
+ return err;
+}
+
+// Visit all children once (at most), because groups can appear in
+// multiple other groups use visitedGroups (protected by
+// rw_queue) to avoid multiple visits. Will stop if an op
+// returns non-NULL.
+// (Used only by ForAllNodes above)
+void GroupTransform::RecurseForAllNodes(dispatch_group_t group, CFErrorRef *err_, bool parallel, bool opExecutesOnGroups, Transform::TransformOperation op)
+{
+ __block CFErrorRef *err = err_;
+ void (^set_error)(CFErrorRef new_err) = ^(CFErrorRef new_err) {
+ if (new_err) {
+ if (!OSAtomicCompareAndSwapPtrBarrier(NULL, (void *)new_err, (void**)err)) {
+ CFRelease(new_err);
+ }
+ }
+ };
+ void (^runOp)(Transform *t) = ^(Transform *t){
+ if (parallel) {
+ dispatch_group_async(group, t->mDispatchQueue, ^{
+ set_error(op(t));
+ });
+ } else {
+ set_error(op(t));
+ }
+ };
+
+ dispatch_group_enter(group);
+ if (opExecutesOnGroups) {
+ runOp(this);
+ }
+
+
+ CFIndex i, lim = CFArrayGetCount(mMembers);
+
+ for(i = 0; i < lim && !*err; ++i) {
+ SecTransformRef tr = CFArrayGetValueAtIndex(mMembers, i);
+ Transform *t = (Transform*)CoreFoundationHolder::ObjectFromCFType(tr);
+
+ if (CFGetTypeID(tr) == SecGroupTransformGetTypeID()) {
+ GroupTransform *g = (GroupTransform*)t;
+ g->RecurseForAllNodes(group, err, parallel, opExecutesOnGroups, op);
+ } else {
+ runOp(t);
+ }
+ }
+
+ dispatch_group_leave(group);
+}
+
+// Return a dot (GraphViz) description of the group.
+// For debugging use. Exact content and style may
+// change. Currently all transforms and attributes
+// are displayed, but only string values are shown
+// (and no meta attributes are indicated).
+CFStringRef GroupTransform::DotForDebugging()
+{
+ __block CFMutableStringRef result = CFStringCreateMutable(NULL, 0);
+ CFStringAppend(result, CFSTR("digraph \"G\" {\n"));
+
+ dispatch_queue_t collect_nodes = dispatch_queue_create("dot-node-collector", NULL);
+ dispatch_group_t complete_nodes = dispatch_group_create();
+ dispatch_queue_t collect_connections = dispatch_queue_create("dot-connection-collector", NULL);
+ dispatch_group_t complete_connections = dispatch_group_create();
+ // Before we reference a node we need it to be declared in the correct
+ // graph cluster, so we defer all the connection output until we
+ // have all the nodes defined.
+ dispatch_suspend(collect_connections);
+
+
+ this->ForAllNodesAsync(true, complete_nodes, ^(Transform *t) {
+ CFStringRef name = t->GetName();
+ __block CFMutableStringRef group_nodes_out = CFStringCreateMutable(NULL, 0);
+ __block CFMutableStringRef group_connections_out = CFStringCreateMutable(NULL, 0);
+ CFStringRef line_out = CFStringCreateWithFormat(NULL, NULL, CFSTR("\tsubgraph \"cluster_%@\" {\n"), name);
+ CFStringAppend(group_nodes_out, line_out);
+ CFRelease(line_out);
+ line_out = NULL;
+
+ CFIndex n_attributes = t->GetAttributeCount();
+ transform_attribute **attributes = (transform_attribute **)alloca(n_attributes * sizeof(transform_attribute *));
+ t->TAGetAll(attributes);
+ CFMutableArrayRef most_dot_names = CFArrayCreateMutable(NULL, n_attributes -1, &kCFTypeArrayCallBacks);
+ for(int i = 0; i < n_attributes; i++) {
+ CFStringRef label = attributes[i]->name;
+ if (attributes[i]->value) {
+ if (CFGetTypeID(attributes[i]->value) == CFStringGetTypeID()) {
+ label = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@=%@"), attributes[i]->name, attributes[i]->value);
+ }
+ }
+ if (!label) {
+ label = CFStringCreateCopy(NULL, attributes[i]->name);
+ }
+
+ CFStringRef dot_node_name = CFStringCreateWithFormat(NULL, NULL, CFSTR("\"%@#%@\""), name, attributes[i]->name);
+ if (CFStringCompare(CFSTR("NAME"), label, 0)) {
+ CFArrayAppendValue(most_dot_names, dot_node_name);
+ }
+ line_out = CFStringCreateWithFormat(NULL, NULL, CFSTR("\t\t%@ [shape=plaintext, label=\"%@\"]\n"), dot_node_name, label);
+ CFStringAppend(group_nodes_out, line_out);
+ CFRelease(line_out);
+ line_out = NULL;
+ CFRelease(label);
+
+ CFIndex n_connections = attributes[i]->connections ? CFArrayGetCount(attributes[i]->connections) : 0;
+ for(int j = 0; j < n_connections; j++) {
+ transform_attribute *connected_to = ah2ta(CFArrayGetValueAtIndex(attributes[i]->connections, j));
+ line_out = CFStringCreateWithFormat(NULL, NULL, CFSTR("\t%@ -> \"%@#%@\"\n"), dot_node_name, connected_to->transform->GetName(), connected_to->name);
+ CFStringAppend(group_connections_out, line_out);
+ CFRelease(line_out);
+ }
+ }
+
+ line_out = CFStringCreateWithFormat(NULL, NULL, CFSTR("\t\t\"%@#NAME\" -> { %@ } [style=invis]\n\t}\n"), name, CFStringCreateByCombiningStrings(NULL, most_dot_names, CFSTR(" ")));
+ CFStringAppend(group_nodes_out, line_out);
+ CFRelease(line_out);
+ if (t->mGroup) {
+ line_out = CFStringCreateWithFormat(NULL, NULL, CFSTR("\t\"%@#NAME\" -> \"%@#NAME\" [style=dotted,weight=5]\n"), name, t->mGroup->GetName());
+ CFStringAppend(group_connections_out, line_out);
+ CFRelease(line_out);
+ }
+ line_out = NULL;
+
+ dispatch_async(collect_nodes, ^(void) {
+ CFStringAppend(result, group_nodes_out);
+ CFRelease(group_nodes_out);
+ });
+ dispatch_group_async(complete_connections, collect_connections, ^(void) {
+ // We don't really need to append to result on the collect_nodes queue
+ // because we happen to know no more work is going on on the collect_nodes
+ // queue, but if that ever changed we would have a hard to track down bug...
+ dispatch_async(collect_nodes, ^(void) {
+ CFStringAppend(result, group_connections_out);
+ CFRelease(group_connections_out);
+ });
+ });
+ });
+
+ dispatch_group_wait(complete_nodes, DISPATCH_TIME_FOREVER);
+ dispatch_release(complete_nodes);
+ dispatch_resume(collect_connections);
+ dispatch_release(collect_connections);
+ dispatch_group_wait(complete_connections, DISPATCH_TIME_FOREVER);
+ dispatch_release(complete_connections);
+
+ dispatch_sync(collect_nodes, ^{
+ CFStringAppend(result, CFSTR("}\n"));
+ });
+ dispatch_release(collect_nodes);
+ return result;
+}