/*++ © 1998 Seagate Software, Inc. All rights reserved. Module Name: hsmrule.cpp Abstract: This component represents a rule for a job's policy. Author: Chuck Bardeen [cbardeen] 29-Oct-1996 Revision History: --*/ #include "stdafx.h" #include "wsb.h" #include "job.h" #include "hsmrule.h" #define WSB_TRACE_IS WSB_TRACE_BIT_JOB // These are defined in nt.h, but it takes all sorts of grief to try to include them. Since // they are just used internally, it isn't even inportant that we have the same definitions. #if !defined(DOS_STAR) #define DOS_STAR (L'<') #endif #if !defined(DOS_QM) #define DOS_QM (L'>') #endif #if !defined(DOS_DOT) #define DOS_DOT (L'"') #endif HRESULT CHsmRule::CompareTo( IN IUnknown* pUnknown, OUT SHORT* pResult ) /*++ Implements: IWsbCollectable::CompareTo(). --*/ { HRESULT hr = S_OK; CComPtr pRule; WsbTraceIn(OLESTR("CHsmRule::CompareTo"), OLESTR("")); try { // Did they give us a valid item to compare to? WsbAssert(0 != pUnknown, E_POINTER); // We need the IWsbBool interface to get the value of the object. WsbAffirmHr(pUnknown->QueryInterface(IID_IHsmRule, (void**) &pRule)); // Compare the rules. hr = CompareToIRule(pRule, pResult); } WsbCatch(hr); WsbTraceOut(OLESTR("CHsmRule::CompareTo"), OLESTR("hr = <%ls>, result = <%ls>"), WsbHrAsString(hr), WsbPtrToShortAsString(pResult)); return(hr); } HRESULT CHsmRule::CompareToIRule( IN IHsmRule* pRule, OUT SHORT* pResult ) /*++ Implements: IHsmRule::CompareToIRule(). --*/ { HRESULT hr = S_OK; OLECHAR* path = 0; OLECHAR* name = 0; WsbTraceIn(OLESTR("CHsmRule::CompareToIRule"), OLESTR("")); try { // Did they give us a valid item to compare to? WsbAssert(0 != pRule, E_POINTER); // Get the path and name. WsbAffirmHr(pRule->GetPath(&path, 0)); WsbAffirmHr(pRule->GetName(&name, 0)); // Compare to the path and name. hr = CompareToPathAndName(path, name, pResult); } WsbCatch(hr); if (0 != path) { WsbFree(path); } if (0 != name) { WsbFree(name); } WsbTraceOut(OLESTR("CHsmRule::CompareToIRule"), OLESTR("hr = <%ls>, result = <%ls>"), WsbHrAsString(hr), WsbPtrToShortAsString(pResult)); return(hr); } HRESULT CHsmRule::CompareToPathAndName( IN OLECHAR* path, IN OLECHAR* name, OUT SHORT* pResult ) /*++ Implements: IHsmRule::CompareToPathAndName(). --*/ { HRESULT hr = S_OK; SHORT aResult = 0; WsbTraceIn(OLESTR("CHsmRule::CompareToPathAndName"), OLESTR("path = <%ls>, name = <%ls>"), path, name); try { // Compare the path. aResult = (SHORT)_wcsicmp(m_path, path); // Compare the name. if (0 == aResult) { aResult = (SHORT)_wcsicmp(m_name, name); } if (0 != aResult) { hr = S_FALSE; } if (0 != pResult) { *pResult = aResult; } } WsbCatch(hr); WsbTraceOut(OLESTR("CHsmRule::CompareToIRule"), OLESTR("hr = <%ls>, result = <%u>"), WsbHrAsString(hr), aResult); return(hr); } HRESULT CHsmRule::Criteria( OUT IWsbCollection** ppCriteria ) /*++ Implements: IHsmRule::Criteria(). --*/ { HRESULT hr = S_OK; try { WsbAssert(0 != ppCriteria, E_POINTER); *ppCriteria = m_pCriteria; m_pCriteria->AddRef(); } WsbCatch(hr); return(hr); } HRESULT CHsmRule::DoesNameContainWildcards( OLECHAR* name ) /*++ Implements: IHsmRule::DoesNameContainWildcards(). --*/ { HRESULT hr = S_FALSE; try { WsbAssert(0 != name, E_POINTER); if (wcscspn(name, OLESTR("*?<>\"")) < wcslen(name)) { hr = S_OK; } } WsbCatch(hr); return(hr); } HRESULT CHsmRule::EnumCriteria( OUT IWsbEnum** ppEnum ) /*++ Implements: IHsmRule::EnumCriteria(). --*/ { HRESULT hr = S_OK; try { WsbAssert(0 != ppEnum, E_POINTER); WsbAffirmHr(m_pCriteria->Enum(ppEnum)); } WsbCatch(hr); return(hr); } HRESULT CHsmRule::FinalConstruct( void ) /*++ Implements: CComObjectRoot::FinalConstruct(). --*/ { HRESULT hr = S_OK; try { WsbAffirmHr(CWsbObject::FinalConstruct()); m_isInclude = TRUE; m_isUserDefined = TRUE; m_isUsedInSubDirs = TRUE; //Create the criteria collection. WsbAffirmHr(CoCreateInstance(CLSID_CWsbOrderedCollection, NULL, CLSCTX_ALL, IID_IWsbCollection, (void**) &m_pCriteria)); } WsbCatch(hr); return(hr); } HRESULT CHsmRule::GetClassID( OUT CLSID* pClsid ) /*++ Implements: IPersist::GetClassID(). --*/ { HRESULT hr = S_OK; WsbTraceIn(OLESTR("CHsmRule::GetClassID"), OLESTR("")); try { WsbAssert(0 != pClsid, E_POINTER); *pClsid = CLSID_CHsmRule; } WsbCatch(hr); WsbTraceOut(OLESTR("CHsmRule::GetClassID"), OLESTR("hr = <%ls>, CLSID = <%ls>"), WsbHrAsString(hr), WsbGuidAsString(*pClsid)); return(hr); } HRESULT CHsmRule::GetName( OUT OLECHAR** pName, IN ULONG bufferSize ) /*++ Implements: IHsmRule::GetName(). --*/ { HRESULT hr = S_OK; try { WsbAssert(0 != pName, E_POINTER); WsbAffirmHr(m_name.CopyTo(pName, bufferSize)); } WsbCatch(hr); return(hr); } HRESULT CHsmRule::GetPath( OUT OLECHAR** pPath, IN ULONG bufferSize ) /*++ Implements: IHsmRule::GetPath(). --*/ { HRESULT hr = S_OK; try { WsbAssert(0 != pPath, E_POINTER); WsbAffirmHr(m_path.CopyTo(pPath, bufferSize)); } WsbCatch(hr); return(hr); } HRESULT CHsmRule::GetSearchName( OUT OLECHAR** pName, IN ULONG bufferSize ) /*++ Implements: IHsmRule::GetSearchName(). --*/ { HRESULT hr = S_OK; try { WsbAssert(0 != pName, E_POINTER); WsbAffirmHr(m_searchName.CopyTo(pName, bufferSize)); } WsbCatch(hr); return(hr); } HRESULT CHsmRule::GetSizeMax( OUT ULARGE_INTEGER* pSize ) /*++ Implements: IPersistStream::GetSizeMax(). --*/ { HRESULT hr = S_OK; CComPtr pPersistStream; ULARGE_INTEGER entrySize; WsbTraceIn(OLESTR("CHsmRule::GetSizeMax"), OLESTR("")); try { WsbAssert(0 != pSize, E_POINTER); // Determine the size for a rule with no criteria. pSize->QuadPart = WsbPersistSize((wcslen(m_path) + 1) * sizeof(OLECHAR)) + WsbPersistSize((wcslen(m_name) + 1) * sizeof(OLECHAR)) + WsbPersistSize((wcslen(m_searchName) + 1) * sizeof(OLECHAR)) + 3 * WsbPersistSizeOf(BOOL) + WsbPersistSizeOf(ULONG); // Now allocate space for the criteria (assume they are all the // same size). WsbAffirmHr(m_pCriteria->QueryInterface(IID_IPersistStream, (void**) &pPersistStream)); WsbAffirmHr(pPersistStream->GetSizeMax(&entrySize)); pSize->QuadPart += entrySize.QuadPart; } WsbCatch(hr); WsbTraceOut(OLESTR("CHsmRule::GetSizeMax"), OLESTR("hr = <%ls>, Size = <%ls>"), WsbHrAsString(hr), WsbPtrToUliAsString(pSize)); return(hr); } HRESULT CHsmRule::IsUserDefined( void ) /*++ Implements: IHsmRule::IsUserDefined(). --*/ { HRESULT hr = S_OK; if (!m_isUserDefined) { hr = S_FALSE; } return(hr); } HRESULT CHsmRule::IsInclude( void ) /*++ Implements: IHsmRule::IsInclude(). --*/ { HRESULT hr = S_OK; if (!m_isInclude) { hr = S_FALSE; } return(hr); } HRESULT CHsmRule::IsNameInExpression( IN OLECHAR* expression, IN OLECHAR* name, IN BOOL ignoreCase ) /*++ Implements: CHsmRule::IsNameInExpression(). --*/ { HRESULT hr = S_FALSE; USHORT nameLength; USHORT expressionLength; WsbTraceIn(OLESTR("CHsmRule::IsNameInExpression"), OLESTR("expression = %ls, name = %ls, ignoreCase = %ls"), expression, name, WsbBoolAsString(ignoreCase)); try { // This is algorithm is from FsRtlIsNameInExpressionPrivate(), but has been rewritten to fit // our coding standards, data structures, and to remove other dependencies on Rtl...() code. // The idea behind the algorithm is pretty simple. We keep track of // all possible locations in the regular expression that are matching // the name. If when the name has been exhausted one of the locations // in the expression is also just exhausted, the name is in the language // defined by the regular expression. WsbAssert(name != 0, E_POINTER); WsbAssert(expression != 0, E_POINTER); nameLength = (SHORT)wcslen(name); expressionLength = (SHORT)wcslen(expression); // If one string is empty return FALSE. If both are empty return TRUE. if ((nameLength == 0) && (expressionLength == 0)) { hr = S_OK; } else if ((nameLength != 0) && (expressionLength != 0)) { // Special case by far the most common wild card search of * if ((expressionLength == 1) && (expression[0] == L'*')) { hr = S_OK; } // Also special case expressions of the form *X. With this and the prior // case we have covered virtually all normal queries. else if (expression[0] == L'*') { // Only special case an expression with a single * if (DoesNameContainWildcards(&expression[1]) == S_FALSE) { // If the name is smaller than the expression, than it isn't a match. Otherwise, // we need to check. if (nameLength >= (expressionLength - 1)) { // Do a simple memory compare if case sensitive, otherwise // we have got to check this one character at a time. if (ignoreCase) { if (_wcsicmp(&expression[1], &name[nameLength - (expressionLength - 1)]) == 0) { hr = S_OK; } } else { if (wcscmp(&expression[1], &name[nameLength - (expressionLength - 1)]) == 0) { hr = S_OK; } } } } } else { // This is the general matching code. Since it is messy, it is put in its // own method. hr = IsNameInExpressionGuts(expression, expressionLength, name, nameLength, ignoreCase); } } } WsbCatch(hr); WsbTraceOut(OLESTR("CHsmRule::IsNameInExpression"), OLESTR("hr = <%ls>"), WsbHrAsString(hr)); return(hr); } HRESULT CHsmRule::IsNameInExpressionGuts( IN OLECHAR* expression, IN USHORT expressionLength, IN OLECHAR* name, IN USHORT nameLength, IN BOOL ignoreCase ) /*++ Implements: CHsmRule::IsNameInExpressionGuts(). --*/ { HRESULT hr = S_FALSE; USHORT nameOffset = 0; OLECHAR nameChar = '0'; USHORT exprOffset = 0; OLECHAR exprChar; BOOL nameFinished = FALSE; ULONG srcCount; ULONG destCount; ULONG previousDestCount; ULONG matchesCount; USHORT* previousMatches = 0; USHORT* currentMatches = 0; USHORT maxState; USHORT currentState; // Walk through the name string, picking off characters. We go one // character beyond the end because some wild cards are able to match // zero characters beyond the end of the string. // // With each new name character we determine a new set of states that // match the name so far. We use two arrays that we swap back and forth // for this purpose. One array lists the possible expression states for // all name characters up to but not including the current one, and other // array is used to build up the list of states considering the current // name character as well. The arrays are then switched and the process // repeated. // // There is not a one-to-one correspondence between state number and // offset into the expression. This is evident from the NFAs in the // initial comment to this function. State numbering is not continuous. // This allows a simple conversion between state number and expression // offset. Each character in the expression can represent one or two // states. * and DOS_STAR generate two states: ExprOffset*2 and // ExprOffset*2 + 1. All other expreesion characters can produce only // a single state. Thus ExprOffset = State/2. // // // Here is a short description of the variables involved: // // nameOffset - The offset of the current name char being processed. // exprOffset - The offset of the current expression char being processed. // // srcCount - Prior match being investigated with current name char // previousDestCount - This is used to prevent entry duplication, see comment // previousMatches - Holds the previous set of matches (the Src array) // // destCount - Next location to put a match assuming current name char // currentMatches - Holds the current set of matches (the Dest array) // // nameFinished - Allows one more itteration through the Matches array // after the name is exhusted (to come *s for example) try { // Since you can get at most two matches per character in the expression, the // biggest arrays you will need is twice the expression length. currentMatches = (USHORT*)WsbAlloc(nameLength * 2 * expressionLength * sizeof(USHORT)); WsbAffirm(0 != currentMatches, E_OUTOFMEMORY); previousMatches = (USHORT*)WsbAlloc(nameLength * 2 * expressionLength * sizeof(USHORT)); WsbAffirm(0 != previousMatches, E_OUTOFMEMORY); previousMatches[0] = 0; matchesCount = 1; maxState = (USHORT)( expressionLength * 2 ); while (!nameFinished) { if (nameOffset < nameLength) { nameChar = name[nameOffset]; nameOffset++; } else { nameFinished = TRUE; // If we have already exhasted the expression, cool. Don't // continue. if (previousMatches[matchesCount - 1] == maxState) { break; } } // Now, for each of the previous stored expression matches, see what // we can do with this name character. srcCount = 0; destCount = 0; previousDestCount = 0; while (srcCount < matchesCount) { USHORT length; // We have to carry on our expression analysis as far as possible // for each character of name, so we loop here until the // expression stops matching. A clue here is that expression // cases that can match zero or more characters end with a // continue, while those that can accept only a single character // end with a break. exprOffset = (USHORT)( ( ( previousMatches[srcCount++] + 1 ) / 2 ) ); length = 0; while (TRUE) { if (exprOffset == expressionLength) { break; } // The first time through the loop we don't want // to increment ExprOffset. exprOffset = (USHORT)( exprOffset + length ); length = 1; currentState = (USHORT)( exprOffset * 2 ); if (exprOffset == expressionLength) { currentMatches[destCount++] = maxState; break; } exprChar = expression[exprOffset]; // * matches any character zero or more times. if (exprChar == L'*') { currentMatches[destCount++] = currentState; currentMatches[destCount++] = (USHORT)( currentState + 1 ); continue; } // DOS_STAR matches any character except . zero or more times. if (exprChar == DOS_STAR) { BOOLEAN iCanEatADot = FALSE; // If we are at a period, determine if we are allowed to // consume it, ie. make sure it is not the last one. if (!nameFinished && (nameChar == '.')) { USHORT offset; for (offset = nameOffset; offset < nameLength; offset++) { if (name[offset] == L'.') { iCanEatADot = TRUE; break; } } } if (nameFinished || (nameChar != L'.') || iCanEatADot) { currentMatches[destCount++] = currentState; currentMatches[destCount++] = (USHORT)( currentState + 1 ); continue; } else { // We are at a period. We can only match zero // characters (ie. the epsilon transition). currentMatches[destCount++] = (USHORT)( currentState + 1 ); continue; } } // The following expreesion characters all match by consuming // a character, thus force the expression, and thus state // forward. currentState += 2; // DOS_QM is the most complicated. If the name is finished, // we can match zero characters. If this name is a '.', we // don't match, but look at the next expression. Otherwise // we match a single character. if (exprChar == DOS_QM) { if (nameFinished || (nameChar == L'.')) { continue; } currentMatches[destCount++] = currentState; break; } // A DOS_DOT can match either a period, or zero characters // beyond the end of name. if (exprChar == DOS_DOT) { if (nameFinished) { continue; } if (nameChar == L'.') { currentMatches[destCount++] = currentState; break; } } // From this point on a name character is required to even // continue, let alone make a match. if (nameFinished) { break; } // If this expression was a '?' we can match it once. if (exprChar == L'?') { currentMatches[destCount++] = currentState; break; } // Finally, check if the expression char matches the name char if (ignoreCase) { if (towlower(exprChar) == towlower(nameChar)) { currentMatches[destCount++] = currentState; break; } } else if (exprChar == nameChar) { currentMatches[destCount++] = currentState; break; } // The expression didn't match so go look at the next // previous match. break; } // Prevent duplication in the destination array. // // Each of the arrays is montonically increasing and non- // duplicating, thus we skip over any source element in the src // array if we just added the same element to the destination // array. This guarentees non-duplication in the dest. array. if ((srcCount < matchesCount) && (previousDestCount < destCount) ) { while (previousDestCount < destCount) { while (previousMatches[srcCount] < currentMatches[previousDestCount]) { srcCount += 1; } previousDestCount += 1; } } } // If we found no matches in the just finished itteration, it's time // to bail. if (destCount == 0) { WsbThrow(S_FALSE); } // Swap the meaning the two arrays { USHORT* tmp; tmp = previousMatches; previousMatches = currentMatches; currentMatches = tmp; } matchesCount = destCount; } currentState = previousMatches[matchesCount - 1]; if (currentState == maxState) { hr = S_OK; } } WsbCatch(hr); // Free the matches buffers that we allocated previously. if (0 != currentMatches) { WsbFree(currentMatches); } if (0 != previousMatches) { WsbFree(previousMatches); } return(hr); } HRESULT CHsmRule::IsUsedInSubDirs( void ) /*++ Implements: IHsmRule::IsUsedInSubDirs(). --*/ { HRESULT hr = S_OK; if (!m_isUsedInSubDirs) { hr = S_FALSE; } return(hr); } HRESULT CHsmRule::Load( IN IStream* pStream ) /*++ Implements: IPersistStream::Load(). --*/ { HRESULT hr = S_OK; CComPtr pPersistStream; CComPtr pCollectable; WsbTraceIn(OLESTR("CHsmRule::Load"), OLESTR("")); try { WsbAssert(0 != pStream, E_POINTER); // Do the easy stuff, but make sure that this order matches the order // in the save method. WsbAffirmHr(WsbLoadFromStream(pStream, &m_path, 0)); WsbAffirmHr(WsbLoadFromStream(pStream, &m_name, 0)); WsbAffirmHr(WsbLoadFromStream(pStream, &m_searchName, 0)); WsbAffirmHr(WsbLoadFromStream(pStream, &m_isUserDefined)); WsbAffirmHr(WsbLoadFromStream(pStream, &m_isInclude)); WsbAffirmHr(WsbLoadFromStream(pStream, &m_isUsedInSubDirs)); // Load all the criteria. WsbAffirmHr(m_pCriteria->QueryInterface(IID_IPersistStream, (void**) &pPersistStream)); WsbAffirmHr(pPersistStream->Load(pStream)); } WsbCatch(hr); WsbTraceOut(OLESTR("CHsmRule::Load"), OLESTR("hr = <%ls>"), WsbHrAsString(hr)); return(hr); } HRESULT CHsmRule::MatchesName( IN OLECHAR* name ) /*++ Implements: IHsmRule::MatchesName(). --*/ { HRESULT hr = S_OK; WsbTraceIn(OLESTR("CHsmRule::MatchesName"), OLESTR("name = <%ls>"), (OLECHAR *)name); try { WsbAssert(0 != name, E_POINTER); // It is assumed that these names have been converted from they way they // might have been input into proper names for IsNameInExpression() // function. See NameToSearchName(). hr = IsNameInExpression(m_searchName, name, TRUE); } WsbCatch(hr); WsbTraceOut(OLESTR("CHsmRule::MatchesName"), OLESTR("hr = <%ls>"), WsbHrAsString(hr)); return(hr); } HRESULT CHsmRule::NameToSearchName( void ) /*++ Implements: CHsmRule::NameToSearchName(). --*/ { HRESULT hr = S_OK; int length; int i; try { WsbAssert(m_name != 0, E_POINTER); // These name alterations are copied from the NT FindFirstFileExW() code; // although the code had to be changed to work with the data structures that // are available. // // *.* -> * // ? -> DOS_QM // . followed by ? or * -> DOS_DOT // * followed by a . -> DOS_STAR if (_wcsicmp(m_name, OLESTR("*.*")) == 0) { m_searchName = OLESTR("*"); } else { m_searchName = m_name; length = wcslen(m_searchName); for (i = 0; i < length; i++) { if ((i != 0) && (m_searchName[i] == L'.') && (m_searchName[i-1] == L'*')) { m_searchName[i-1] = DOS_STAR; } if ((m_searchName[i] == L'?') || (m_searchName[i] == L'*')) { if (m_searchName[i] == L'?') { m_searchName[i] = DOS_QM; } if ((i != 0) && (m_searchName[i-1] == L'.')) { m_searchName[i-1] = DOS_DOT; } } } } } WsbCatch(hr); return(hr); } HRESULT CHsmRule::Save( IN IStream* pStream, IN BOOL clearDirty ) /*++ Implements: IPersistStream::Save(). --*/ { HRESULT hr = S_OK; CComPtr pPersistStream; WsbTraceIn(OLESTR("CHsmRule::Save"), OLESTR("clearDirty = <%ls>"), WsbBoolAsString(clearDirty)); try { WsbAssert(0 != pStream, E_POINTER); // Do the easy stuff, but make sure that this order matches the order // in the save method. WsbAffirmHr(WsbSaveToStream(pStream, m_path)); WsbAffirmHr(WsbSaveToStream(pStream, m_name)); WsbAffirmHr(WsbSaveToStream(pStream, m_searchName)); WsbAffirmHr(WsbSaveToStream(pStream, m_isUserDefined)); WsbAffirmHr(WsbSaveToStream(pStream, m_isInclude)); WsbAffirmHr(WsbSaveToStream(pStream, m_isUsedInSubDirs)); // Save off all the criteria. WsbAffirmHr(m_pCriteria->QueryInterface(IID_IPersistStream, (void**) &pPersistStream)); WsbAffirmHr(pPersistStream->Save(pStream, clearDirty)); // If we got it saved and we were asked to clear the dirty bit, then // do so now. if (clearDirty) { m_isDirty = FALSE; } } WsbCatch(hr); WsbTraceOut(OLESTR("CHsmRule::Save"), OLESTR("hr = <%ls>"), WsbHrAsString(hr)); return(hr); } HRESULT CHsmRule::SetIsInclude( IN BOOL isInclude ) /*++ Implements: IHsmRule::SetIsInclude(). --*/ { m_isInclude = isInclude; m_isDirty = TRUE; return(S_OK); } HRESULT CHsmRule::SetIsUserDefined( IN BOOL isUserDefined ) /*++ Implements: IHsmRule::SetIsUserDefined(). --*/ { m_isUserDefined = isUserDefined; m_isDirty = TRUE; return(S_OK); } HRESULT CHsmRule::SetIsUsedInSubDirs( IN BOOL isUsed ) /*++ Implements: IHsmRule::SetIsUsedInSubDirs(). --*/ { m_isUsedInSubDirs = isUsed; m_isDirty = TRUE; return(S_OK); } HRESULT CHsmRule::SetName( IN OLECHAR* name ) /*++ Implements: IHsmRule::SetName(). --*/ { HRESULT hr = S_OK; try { m_name = name; WsbAffirmHr(NameToSearchName()); m_isDirty = TRUE; } WsbCatch(hr); return(hr); } HRESULT CHsmRule::SetPath( IN OLECHAR* path ) /*++ Implements: IHsmRule::SetPath(). --*/ { HRESULT hr = S_OK; try { m_path = path; m_isDirty = TRUE; } WsbCatch(hr); return(hr); } HRESULT CHsmRule::Test( USHORT* passed, USHORT* failed ) /*++ Implements: IWsbTestable::Test(). --*/ { HRESULT hr = S_OK; try { WsbAssert(0 != passed, E_POINTER); WsbAssert(0 != failed, E_POINTER); *passed = 0; *failed = 0; } WsbCatch(hr); return(hr); }