osaf/services/saf/smfsv/smfd/SmfUtils.cc | 22 +
osaf/services/saf/smfsv/smfd/SmfUtils.hh | 8 +
osaf/services/saf/smfsv/smfd/smfd_campaign_oi.cc | 380 ++++++++++++----------
3 files changed, 244 insertions(+), 166 deletions(-)
SMF allows objects to be created and deleted regardless of DN.
Without this patch the deletion of objects needed specific RDN to be accepted.
diff --git a/osaf/services/saf/smfsv/smfd/SmfUtils.cc
b/osaf/services/saf/smfsv/smfd/SmfUtils.cc
--- a/osaf/services/saf/smfsv/smfd/SmfUtils.cc
+++ b/osaf/services/saf/smfsv/smfd/SmfUtils.cc
@@ -238,6 +238,28 @@ SmfImmUtils::classDescriptionMemoryFree(
}
//
------------------------------------------------------------------------------
+// getClassNameForObject()
+//
------------------------------------------------------------------------------
+bool
+SmfImmUtils::getClassNameForObject(const std::string& i_dn, std::string&
o_className)
+{
+ SaImmAttrValuesT_2 **attributes;
+ if (this->getObject(i_dn, &attributes) == false) {
+ LOG_NO("Failed to get object %s", i_dn.c_str());
+ return false;
+ }
+
+ o_className = immutil_getStringAttr((const SaImmAttrValuesT_2
**)attributes,
+ SA_IMM_ATTR_CLASS_NAME, 0);
+ if (o_className.empty()) {
+ LOG_NO("Failed to get class name for object %s", i_dn.c_str());
+ return false;
+ }
+
+ return true;
+}
+
+//
------------------------------------------------------------------------------
// getObject()
//
------------------------------------------------------------------------------
bool
diff --git a/osaf/services/saf/smfsv/smfd/SmfUtils.hh
b/osaf/services/saf/smfsv/smfd/SmfUtils.hh
--- a/osaf/services/saf/smfsv/smfd/SmfUtils.hh
+++ b/osaf/services/saf/smfsv/smfd/SmfUtils.hh
@@ -109,6 +109,14 @@ class SmfImmUtils {
bool classDescriptionMemoryFree(SaImmAttrDefinitionT_2 **
i_attributeDefs);
///
+/// Purpose: Get the class name for an IMM object.
+/// @param i_dn DN of the object to read the class name from.
+/// @param o_className Resulting class name
+/// @return True if successful, otherwise false
+///
+ bool getClassNameForObject(const std::string& i_dn, std::string&
o_className);
+
+///
/// Purpose: Get all attributes for an IMM object.
/// @param i_dn DN of the object to get.
/// @param o_attributes Resulting attribute values
diff --git a/osaf/services/saf/smfsv/smfd/smfd_campaign_oi.cc
b/osaf/services/saf/smfsv/smfd/smfd_campaign_oi.cc
--- a/osaf/services/saf/smfsv/smfd/smfd_campaign_oi.cc
+++ b/osaf/services/saf/smfsv/smfd/smfd_campaign_oi.cc
@@ -49,6 +49,13 @@ static const SaImmClassNameT campaignCla
static const SaImmClassNameT smfConfigClassName = (SaImmClassNameT)
"OpenSafSmfConfig";
static const SaImmClassNameT smfSwBundleClassName = (SaImmClassNameT)
"SaSmfSwBundle";
+typedef enum {
+ SMF_CLASS_UNKNOWN = 0,
+ SMF_CLASS_CAMPAIGN = 1,
+ SMF_CLASS_BUNDLE = 2,
+ SMF_CLASS_CONFIG = 3
+} smf_classes_t;
+
/**
* Campaign Admin operation handling. This function is executed as an
* IMM callback.
@@ -184,187 +191,227 @@ static SaAisErrorT saImmOiCcbCompletedCa
}
/*
- ** "check that the sequence of change requests contained in the CCB is
- ** valid and that no errors will be generated when these changes
- ** are applied."
- */
+ ** "check that the sequence of change requests contained in the CCB is
+ ** valid and that no errors will be generated when these changes
+ ** are applied."
+ */
ccbUtilOperationData = ccbUtilCcbData->operationListHead;
while (ccbUtilOperationData != NULL) {
- switch (ccbUtilOperationData->operationType) {
- case CCBUTIL_CREATE:
- {
- //Handle the campaign object
- if
(strcmp(ccbUtilOperationData->param.create.className, campaignClassName) == 0) {
- SmfCampaign
test(ccbUtilOperationData->param.create.parentName,
-
ccbUtilOperationData->param.create.attrValues);
+ switch (ccbUtilOperationData->operationType) {
+ case CCBUTIL_CREATE:
+ {
+ //Handle the campaign object
+ if
(strcmp(ccbUtilOperationData->param.create.className, campaignClassName) == 0) {
+ SmfCampaign
test(ccbUtilOperationData->param.create.parentName,
+
ccbUtilOperationData->param.create.attrValues);
+ //Save the class name enum for use in later
phases
+ ccbUtilOperationData->userData =
(void*)SMF_CLASS_CAMPAIGN;
+ TRACE("Create campaign %s",
test.getDn().c_str()); /* Creation always allowed */
- TRACE("Create campaign %s",
test.getDn().c_str()); /* Creation always allowed */
- //Handle the SaSmfSwBundle object
- } else if
(strcmp(ccbUtilOperationData->param.create.className, smfSwBundleClassName) ==
0) {
- TRACE("Create a software bundle"); /*
Creation always allowed */
- //Handle the OpenSAFSmfConfig object
- } else if
(strcmp(ccbUtilOperationData->param.create.className, smfConfigClassName) == 0)
{
- LOG_NO("OpenSafSmfConfig object must be
initially created");
- rc = SA_AIS_ERR_BAD_OPERATION;
- goto done;
- } else {
- LOG_NO("Unknown class name %s, can't be
created",
-
ccbUtilOperationData->param.create.className);
- rc = SA_AIS_ERR_BAD_OPERATION;
- goto done;
- }
- break;
- }
+ //Handle the SaSmfSwBundle object
+ } else if
(strcmp(ccbUtilOperationData->param.create.className, smfSwBundleClassName) ==
0) {
+ //Save the class name enum for use in later
phases
+ ccbUtilOperationData->userData =
(void*)SMF_CLASS_BUNDLE;
+ TRACE("Create a software bundle"); /* Creation
always allowed */
- case CCBUTIL_DELETE:
- {
- //Handle the campaign object
- if (strncmp((char
*)ccbUtilOperationData->param.deleteOp.objectName->value, "safSmfCampaign=",
15) == 0) {
- TRACE("Delete campaign %s",
ccbUtilOperationData->param.deleteOp.objectName->value);
+ //Handle the OpenSAFSmfConfig object
+ } else if
(strcmp(ccbUtilOperationData->param.create.className, smfConfigClassName) == 0)
{
+ //Save the class name enum for use in later
phases
+ ccbUtilOperationData->userData =
(void*)SMF_CLASS_CONFIG;
+ LOG_NO("OpenSafSmfConfig object must be
initially created");
+ rc = SA_AIS_ERR_BAD_OPERATION;
+ goto done;
- SmfCampaign *campaign =
-
SmfCampaignList::instance()->get(ccbUtilOperationData->param.deleteOp.objectName);
- if (campaign == NULL) {
- LOG_NO("Campaign %s doesn't exists,
can't be deleted",
-
ccbUtilOperationData->param.deleteOp.objectName->value);
- rc = SA_AIS_ERR_BAD_OPERATION;
- goto done;
- }
+ //Handle unknown
+ } else {
+ //Save the class name enum for use in later
phases
+ ccbUtilOperationData->userData =
(void*)SMF_CLASS_UNKNOWN;
+ LOG_NO("Unknown class name %s, can't be
created",
+
ccbUtilOperationData->param.create.className);
+ rc = SA_AIS_ERR_BAD_OPERATION;
+ goto done;
+ }
+ break;
+ }
- if (campaign->executing() == true) {
- LOG_NO("Campaign %s in state %u, can't
be deleted",
-
ccbUtilOperationData->param.deleteOp.objectName->value,
- campaign->getState());
- rc = SA_AIS_ERR_BAD_OPERATION;
- goto done;
- }
- else if ((SmfCampaignThread::instance() !=
NULL) &&
-
(SmfCampaignThread::instance()->campaign() == campaign)) {
- /* Campaign is executing prereq tests
(in state INITIAL) */
- LOG_NO("Campaign %s is executing, can't
be deleted",
-
ccbUtilOperationData->param.deleteOp.objectName->value);
- rc = SA_AIS_ERR_BAD_OPERATION;
- goto done;
+ case CCBUTIL_DELETE:
+ {
+ //Get the DN to the object to delete and read the
class name
+ const std::string objToDelete = (char
*)ccbUtilOperationData->param.deleteOp.objectName->value;
+
+ std::string className;
+ if (immUtil.getClassNameForObject(objToDelete,
className) == false) {
+ LOG_NO("Failed to get class name for object to
delete %s", objToDelete.c_str());
+ rc = SA_AIS_ERR_BAD_OPERATION;
+ goto done;
+ }
+
+ //Handle the campaign object
+ if (className == campaignClassName ) {
+ TRACE("Delete campaign %s",
objToDelete.c_str());
+ //Save the class name enum for use in later
phases
+ ccbUtilOperationData->userData =
(void*)SMF_CLASS_CAMPAIGN;
+
+ //Find Campaign object
+ SmfCampaign *campaign =
+
SmfCampaignList::instance()->get(ccbUtilOperationData->param.deleteOp.objectName);
//objToDelete
+ if (campaign == NULL) {
+ LOG_NO("Campaign %s doesn't exists,
can't be deleted", objToDelete.c_str());
+ rc = SA_AIS_ERR_BAD_OPERATION;
+ goto done;
}
- //Handle the OpenSAFSmfConfig object
- }else if
(strncmp((char*)ccbUtilOperationData->param.deleteOp.objectName->value,
"smfConfig=", 10) == 0) {
- LOG_NO("Deletion of SMF configuration object
%s, not allowed",
-
ccbUtilOperationData->param.deleteOp.objectName->value);
- rc = SA_AIS_ERR_BAD_OPERATION;
- goto done;
- //Handle the SaSmfSwBundle object
- }else if
(strncmp((char*)ccbUtilOperationData->param.deleteOp.objectName->value,
"safSmfBundle=", 13) == 0) {
- TRACE("Delete software bundle %s",
ccbUtilOperationData->param.deleteOp.objectName->value);
- //Check if the bundle is in use.
- //Search for installed bundles
- std::list < std::string > objectList;
- std::list < std::string >::const_iterator objit;
- if(immUtil.getChildren("", objectList,
SA_IMM_SUBTREE, "SaAmfNodeSwBundle") == false){
- LOG_NO("Fail to search for
SaAmfNodeSwBundle instances.");
- rc = SA_AIS_ERR_BAD_OPERATION;
- goto done;
- }
- //Match the names of the installed bundles with
the bundle requested to be removed
- std::string swBundleToRemove = (const
char*)ccbUtilOperationData->param.deleteOp.objectName->value;
- std::string::size_type pos1 =
sizeof("safInstalledSwBundle=") - 1;
+ if (campaign->executing() == true) {
+ LOG_NO("Campaign %s in state %u, can't
be deleted", objToDelete.c_str(), campaign->getState());
+ rc = SA_AIS_ERR_BAD_OPERATION;
+ goto done;
+ } else if ((SmfCampaignThread::instance() !=
NULL) &&
+
(SmfCampaignThread::instance()->campaign() == campaign)) {
+ //Campaign is executing prereq tests
(in state INITIAL)
+ LOG_NO("Campaign %s is executing,
can't be deleted", objToDelete.c_str());
+ rc = SA_AIS_ERR_BAD_OPERATION;
+ goto done;
+ }
- for (objit = objectList.begin(); objit !=
objectList.end(); ++objit) {
- std::string::size_type pos2 =
(*objit).find(",");
- if (pos2 == std::string::npos) {
-
LOG_NO("saImmOiCcbCompletedCallback:: Could not find separator \",\" in %s",
(*objit).c_str());
- rc = SA_AIS_ERR_BAD_OPERATION;
- goto done;
- }
- std::string swBundleInstalled =
(*objit).substr(pos1, pos2 - pos1);
- if (swBundleInstalled.size() == 0) {
-
LOG_NO("saImmOiCcbCompletedCallback:: Could not find installed bundle from %s",
(*objit).c_str());
- rc = SA_AIS_ERR_BAD_OPERATION;
- goto done;
- }
+ //Handle the OpenSAFSmfConfig object
+ } else if (className == smfConfigClassName){
+ //Save the class name enum for use in later
phases
+ ccbUtilOperationData->userData =
(void*)SMF_CLASS_CONFIG;
+ LOG_NO("Deletion of SMF configuration object
%s, not allowed", objToDelete.c_str());
+ rc = SA_AIS_ERR_BAD_OPERATION;
+ goto done;
- if (swBundleInstalled ==
swBundleToRemove){
- LOG_NO("SwBundle %s is in use,
can not be removed", swBundleToRemove.c_str());
- rc = SA_AIS_ERR_BAD_OPERATION;
- goto done;
- }
- }
+ //Handle the SaSmfSwBundle object
+ } else if (className == smfSwBundleClassName){
+ TRACE("Delete software bundle %s",
objToDelete.c_str());
+ //Save the class name enum for use in later
phases
+ ccbUtilOperationData->userData =
(void*)SMF_CLASS_BUNDLE;
- goto done;
- //Handle any unknown object
- } else {
- LOG_NO("Unknown object %s, can't be deleted",
-
ccbUtilOperationData->param.deleteOp.objectName->value);
- rc = SA_AIS_ERR_BAD_OPERATION;
- goto done;
- }
- break;
- }
+ //Check if the bundle is in use.
+ //Search for installed bundles
+ std::list < std::string > objectList;
+ std::list < std::string >::const_iterator
objit;
+ if(immUtil.getChildren("", objectList,
SA_IMM_SUBTREE, "SaAmfNodeSwBundle") == false){
+ LOG_NO("Fail to search for
SaAmfNodeSwBundle instances.");
+ rc = SA_AIS_ERR_BAD_OPERATION;
+ goto done;
+ }
+ //Match the names of the installed bundles
with the bundle requested to be removed
+ std::string swBundleToRemove = objToDelete;
+ std::string::size_type pos1 =
sizeof("safInstalledSwBundle=") - 1;
- case CCBUTIL_MODIFY:
- {
- //Handle the campaign object
- if (strncmp((char
*)ccbUtilOperationData->param.modify.objectName->value, "safSmfCampaign=", 15)
== 0) {
- TRACE("Modify campaign %s",
ccbUtilOperationData->param.modify.objectName->value);
-
- /* Find Campaign object */
- SmfCampaign *campaign =
-
SmfCampaignList::instance()->get(ccbUtilOperationData->param.modify.objectName);
- if (campaign == NULL) {
- LOG_NO("Campaign %s not found,
can't be modified",
-
ccbUtilOperationData->param.modify.objectName->value);
- rc = SA_AIS_ERR_BAD_OPERATION;
- goto done;
- }
-
- if (campaign->executing() == true) {
- LOG_NO("Campaign %s in state
%u, can't be modified",
-
ccbUtilOperationData->param.modify.objectName->value,
- campaign->getState());
+ for (objit = objectList.begin(); objit !=
objectList.end(); ++objit) {
+ std::string::size_type pos2 =
(*objit).find(",");
+ if (pos2 == std::string::npos) {
+
LOG_NO("saImmOiCcbCompletedCallback:: Could not find separator \",\" in %s",
(*objit).c_str());
rc = SA_AIS_ERR_BAD_OPERATION;
goto done;
}
- else if
((SmfCampaignThread::instance() != NULL) &&
-
(SmfCampaignThread::instance()->campaign() == campaign)) {
- /* Campaign is executing
prereq tests (in state INITIAL) */
- LOG_NO("Campaign %s is
executing, can't be modified",
-
ccbUtilOperationData->param.modify.objectName->value);
+ std::string swBundleInstalled =
(*objit).substr(pos1, pos2 - pos1);
+ if (swBundleInstalled.size() == 0) {
+
LOG_NO("saImmOiCcbCompletedCallback:: Could not find installed bundle from %s",
(*objit).c_str());
rc = SA_AIS_ERR_BAD_OPERATION;
goto done;
}
- rc =
campaign->verify(ccbUtilOperationData->param.modify.attrMods);
- if (rc != SA_AIS_OK) {
- LOG_NO("Campaign %s attribute
modification fail, wrong parameter content",
-
ccbUtilOperationData->param.modify.objectName->value);
- goto done;
- }
- //Handle the OpenSAFSmfConfig object
- } else if
(strncmp((char*)ccbUtilOperationData->param.modify.objectName->value,
"smfConfig=", 10) == 0) {
- //Modification of OpenSAFSmfConfig
object is always allowed.
- //The SMF control block structure is
re-read at saImmOiCcbApplyCallback
- TRACE("Modification of object %s",
ccbUtilOperationData->param.modify.objectName->value);
+ if (swBundleInstalled ==
swBundleToRemove){
+ LOG_NO("SwBundle %s is in use,
can not be removed", swBundleToRemove.c_str());
+ rc = SA_AIS_ERR_BAD_OPERATION;
+ goto done;
+ }
+ }
+
+ goto done;
+ //Handle any unknown object
+ } else {
+ LOG_NO("Unknown object %s, can't be deleted",
objToDelete.c_str());
+ //Save the class name enum for use in later
phases
+ ccbUtilOperationData->userData =
(void*)SMF_CLASS_UNKNOWN;
+
+ rc = SA_AIS_ERR_BAD_OPERATION;
+ goto done;
+ }
+ break;
+ }
+
+ case CCBUTIL_MODIFY:
+ {
+ //Get the DN to the object to modify and read the
class name
+ std::string objToModify = (char
*)ccbUtilOperationData->param.modify.objectName->value;
- //Handle the SaSmfSwBundle object
- } else if (strncmp((char
*)ccbUtilOperationData->param.modify.objectName->value, "safSmfBundle=", 13) ==
0) {
- //Always allow modification
- TRACE("Modification of object %s",
ccbUtilOperationData->param.modify.objectName->value);
+ std::string className;
+ if (immUtil.getClassNameForObject(objToModify,
className) == false) {
+ LOG_NO("Failed to get class name for object to
modify %s", objToModify.c_str());
+ rc = SA_AIS_ERR_BAD_OPERATION;
+ goto done;
+ }
- //Handle any unknown object
- } else {
- LOG_NO("Unknown object %s, can't be
modified",
-
ccbUtilOperationData->param.modify.objectName->value);
- rc = SA_AIS_ERR_BAD_OPERATION;
- goto done;
- }
+ //Handle the campaign object
+ if (className == campaignClassName) {
+ TRACE("Modify campaign %s",
objToModify.c_str());
+ //Save the class name enum for use in later
phases
+ ccbUtilOperationData->userData =
(void*)SMF_CLASS_CAMPAIGN;
- break;
- }
- }
- ccbUtilOperationData = ccbUtilOperationData->next;
+ //Find Campaign object
+ SmfCampaign *campaign =
+
SmfCampaignList::instance()->get(ccbUtilOperationData->param.modify.objectName);
//objToModify
+ if (campaign == NULL) {
+ LOG_NO("Campaign %s not found, can't
be modified", objToModify.c_str());
+ rc = SA_AIS_ERR_BAD_OPERATION;
+ goto done;
+ }
+
+ if (campaign->executing() == true) {
+ LOG_NO("Campaign %s in state %u, can't
be modified",
+ objToModify.c_str(),
+ campaign->getState());
+ rc = SA_AIS_ERR_BAD_OPERATION;
+ goto done;
+ } else if ((SmfCampaignThread::instance() !=
NULL) &&
+
(SmfCampaignThread::instance()->campaign() == campaign)) {
+ //Campaign is executing prereq tests
(in state INITIAL)
+ LOG_NO("Campaign %s is executing,
can't be modified", objToModify.c_str());
+ rc = SA_AIS_ERR_BAD_OPERATION;
+ goto done;
+ }
+
+ rc =
campaign->verify(ccbUtilOperationData->param.modify.attrMods);
+ if (rc != SA_AIS_OK) {
+ LOG_NO("Campaign %s attribute
modification fail, wrong parameter content", objToModify.c_str());
+ goto done;
+ }
+
+ //Handle the OpenSAFSmfConfig object
+ } else if (className == smfConfigClassName){
+ //Modification of OpenSAFSmfConfig object is
always allowed.
+ //The SMF control block structure is re-read
at saImmOiCcbApplyCallback
+ TRACE("Modification of object %s",
objToModify.c_str());
+ //Save the class name enum for use in later
phases
+ ccbUtilOperationData->userData =
(void*)SMF_CLASS_CONFIG;
+
+ //Handle the SaSmfSwBundle object
+ } else if (className == smfSwBundleClassName){
+ //Always allow modification
+ TRACE("Modification of object %s",
objToModify.c_str());
+ //Save the class name enum for use in later
phases
+ ccbUtilOperationData->userData =
(void*)SMF_CLASS_BUNDLE;
+
+ //Handle any unknown object
+ } else {
+ LOG_NO("Unknown object %s, can't be modified"
,objToModify.c_str());
+ //Save the class name enum for use in later
phases
+ ccbUtilOperationData->userData =
(void*)SMF_CLASS_UNKNOWN;
+ rc = SA_AIS_ERR_BAD_OPERATION;
+ goto done;
+ }
+
+ break;
+ }
+ }
+ ccbUtilOperationData = ccbUtilOperationData->next;
}
- done:
+done:
TRACE_LEAVE();
return rc;
}
@@ -374,6 +421,7 @@ static void saImmOiCcbApplyCallback(SaIm
struct CcbUtilCcbData *ccbUtilCcbData;
struct CcbUtilOperationData *ccbUtilOperationData;
bool openSAFSmfConfigApply = false;
+ SmfImmUtils immUtil;
TRACE_ENTER();
@@ -388,7 +436,7 @@ static void saImmOiCcbApplyCallback(SaIm
case CCBUTIL_CREATE:
{
//Handle the campaign object
- if
(strcmp(ccbUtilOperationData->param.create.className, campaignClassName) == 0) {
+ if ((long)ccbUtilOperationData->userData ==
SMF_CLASS_CAMPAIGN) {
SmfCampaign *newCampaign =
new
SmfCampaign(ccbUtilOperationData->param.create.parentName,
ccbUtilOperationData->param.create.attrValues);
@@ -401,8 +449,9 @@ static void saImmOiCcbApplyCallback(SaIm
case CCBUTIL_DELETE:
{
- if (strncmp((char
*)ccbUtilOperationData->param.deleteOp.objectName->value, "safSmfCampaign=",
15) == 0) {
- TRACE("Deleting campaign %s",
ccbUtilOperationData->param.deleteOp.objectName->value);
+ //Handle the campaign object
+ if ((long)ccbUtilOperationData->userData ==
SMF_CLASS_CAMPAIGN) {
+ TRACE("Deleting campaign %s",
(char*)ccbUtilOperationData->param.deleteOp.objectName->value);
SmfCampaignList::instance()->del(ccbUtilOperationData->param.deleteOp.objectName);
}
break;
@@ -411,23 +460,22 @@ static void saImmOiCcbApplyCallback(SaIm
case CCBUTIL_MODIFY:
{
//Handle the campaign object
- if (strncmp((char
*)ccbUtilOperationData->param.modify.objectName->value, "safSmfCampaign=", 15)
== 0) {
- TRACE("Modifying campaign %s",
ccbUtilOperationData->param.modify.objectName->value);
+ if ((long)ccbUtilOperationData->userData ==
SMF_CLASS_CAMPAIGN) {
+ TRACE("Modifying campaign %s",
(char*)ccbUtilOperationData->param.modify.objectName->value);
/* Find Campaign object */
SmfCampaign *campaign =
SmfCampaignList::instance()->get(ccbUtilOperationData->param.modify.objectName);
if (campaign == NULL) {
- LOG_NO("Campaign %s not found",
-
ccbUtilOperationData->param.modify.objectName->value);
+ LOG_NO("Campaign %s not found", (char
*)ccbUtilOperationData->param.modify.objectName->value);
goto done;
}
campaign->modify(ccbUtilOperationData->param.modify.attrMods);
//Handle the OpenSAFSmfConfig object
- }else if
(strncmp((char*)ccbUtilOperationData->param.modify.objectName->value,
"smfConfig=", 10) == 0) {
- TRACE("Modifying configuration object %s",
ccbUtilOperationData->param.modify.objectName->value);
+ } else if ((long)ccbUtilOperationData->userData ==
SMF_CLASS_CONFIG){
+ TRACE("Modifying configuration object %s",
(char *)ccbUtilOperationData->param.modify.objectName->value);
openSAFSmfConfigApply = true;
}
break;
------------------------------------------------------------------------------
Subversion Kills Productivity. Get off Subversion & Make the Move to Perforce.
With Perforce, you get hassle-free workflows. Merge that actually works.
Faster operations. Version large binaries. Built-in WAN optimization and the
freedom to use Git, Perforce or both. Make the move to Perforce.
http://pubads.g.doubleclick.net/gampad/clk?id=122218951&iu=/4140/ostg.clktrk
_______________________________________________
Opensaf-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/opensaf-devel