/* Query - query parsing and evaluation * * The pattern matching is roughly based on code originally written * by J. Kercheval, and on code written by Kenneth Almquist, though * it shares no code. * * Copyright 2001-2006, Axel Dörfler, axeld@pinc-software.de. * This file may be used under the terms of the MIT License. */ // Adjusted by Ingo Weinhold for usage in RAM FS. #include "Query.h" #include "DebugSupport.h" #include "Directory.h" #include "Entry.h" #include "Misc.h" #include "Node.h" #include "Volume.h" #include "Index.h" #include #include #include #include #include #include #include // IndexWrapper // constructor IndexWrapper::IndexWrapper(Volume *volume) : fVolume(volume), fIndex(NULL) { } // SetTo status_t IndexWrapper::SetTo(const char *name) { fIndex = NULL; if (fVolume) fIndex = fVolume->FindIndex(name); return (fIndex ? B_OK : B_ENTRY_NOT_FOUND); } // Unset void IndexWrapper::Unset() { fIndex = NULL; } // Type uint32 IndexWrapper::Type() const { return (fIndex ? fIndex->GetType() : 0); } // GetSize off_t IndexWrapper::GetSize() const { // Compute a fake "index size" based on the number of entries // (1024 + 16 * entry count), so we don't need to adjust the code using it. return 1024LL + (fIndex ? fIndex->CountEntries() : 0) * 16LL; } // KeySize int32 IndexWrapper::KeySize() const { return (fIndex ? fIndex->GetKeyLength() : 0); } // IndexIterator // constructor IndexIterator::IndexIterator(IndexWrapper *indexWrapper) : fIndexWrapper(indexWrapper), fIterator(), fInitialized(false) { } // Find status_t IndexIterator::Find(const uint8 *const key, size_t keyLength) { status_t error = B_ENTRY_NOT_FOUND; if (fIndexWrapper && fIndexWrapper->fIndex) { // TODO: We actually don't want an exact Find() here, but rather a // FindClose(). fInitialized = fIndexWrapper->fIndex->Find(key, keyLength, &fIterator); if (fInitialized) error = B_OK; } return error; } // Rewind status_t IndexIterator::Rewind() { status_t error = B_ENTRY_NOT_FOUND; if (fIndexWrapper && fIndexWrapper->fIndex) { fInitialized = fIndexWrapper->fIndex->GetIterator(&fIterator); if (fInitialized) error = B_OK; } return error; } // GetNextEntry status_t IndexIterator::GetNextEntry(uint8 *buffer, uint16 *_keyLength, size_t /*bufferSize*/, Entry **_entry) { status_t error = B_ENTRY_NOT_FOUND; if (fIndexWrapper && fIndexWrapper->fIndex) { // init iterator, if not done yet if (!fInitialized) { fIndexWrapper->fIndex->GetIterator(&fIterator); fInitialized = true; } // get key size_t keyLength; if (Entry *entry = fIterator.GetCurrent(buffer, &keyLength)) { *_keyLength = keyLength; *_entry = entry; error = B_OK; } // get next entry fIterator.GetNext(); } return error; } // compare_integral template static inline int compare_integral(const Key &a, const Key &b) { if (a < b) return -1; else if (a > b) return 1; return 0; } // compare_keys static int compare_keys(const uint8 *key1, size_t length1, const uint8 *key2, size_t length2, uint32 type) { switch (type) { case B_INT32_TYPE: return compare_integral(*(int32*)key1, *(int32*)key2); case B_UINT32_TYPE: return compare_integral(*(uint32*)key1, *(uint32*)key2); case B_INT64_TYPE: return compare_integral(*(int64*)key1, *(int64*)key2); case B_UINT64_TYPE: return compare_integral(*(uint64*)key1, *(uint64*)key2); case B_FLOAT_TYPE: return compare_integral(*(float*)key1, *(float*)key2); case B_DOUBLE_TYPE: return compare_integral(*(double*)key1, *(double*)key2); case B_STRING_TYPE: { int result = strncmp((const char*)key1, (const char*)key2, min(length1, length2)); if (result == 0) { result = compare_integral(strnlen((const char*)key1, length1), strnlen((const char*)key2, length2)); } return result; } } return -1; } // compareKeys static inline int compareKeys(uint32 type, const uint8 *key1, size_t length1, const uint8 *key2, size_t length2) { return compare_keys(key1, length1, key2, length2, type); } // The parser has a very static design, but it will do what is required. // // ParseOr(), ParseAnd(), ParseEquation() are guarantying the operator // precedence, that is =,!=,>,<,>=,<= .. && .. ||. // Apparently, the "!" (not) can only be used with brackets. // // If you think that there are too few NULL pointer checks in some places // of the code, just read the beginning of the query constructor. // The API is not fully available, just the Query and the Expression class // are. enum ops { OP_NONE, OP_AND, OP_OR, OP_EQUATION, OP_EQUAL, OP_UNEQUAL, OP_GREATER_THAN, OP_LESS_THAN, OP_GREATER_THAN_OR_EQUAL, OP_LESS_THAN_OR_EQUAL, }; enum match { NO_MATCH = 0, MATCH_OK = 1, MATCH_BAD_PATTERN = -2, MATCH_INVALID_CHARACTER }; // return values from isValidPattern() enum { PATTERN_INVALID_ESCAPE = -3, PATTERN_INVALID_RANGE, PATTERN_INVALID_SET }; union value { int64 Int64; uint64 Uint64; int32 Int32; uint32 Uint32; float Float; double Double; char CString[kMaxIndexKeyLength]; }; // B_MIME_STRING_TYPE is defined in storage/Mime.h, but we // don't need the whole file here; the type can't change anyway #ifndef _MIME_H # define B_MIME_STRING_TYPE 'MIMS' #endif class Term { public: Term(int8 op) : fOp(op), fParent(NULL) {} virtual ~Term() {} int8 Op() const { return fOp; } void SetParent(Term *parent) { fParent = parent; } Term *Parent() const { return fParent; } virtual status_t Match(Entry *entry, Node* node, const char *attribute = NULL, int32 type = 0, const uint8 *key = NULL, size_t size = 0) = 0; virtual void Complement() = 0; virtual void CalculateScore(IndexWrapper &index) = 0; virtual int32 Score() const = 0; virtual status_t InitCheck() = 0; virtual bool NeedsEntry() = 0; #if DEBUG virtual void PrintToStream() = 0; #endif protected: int8 fOp; Term *fParent; }; // Although an Equation object is quite independent from the volume on which // the query is run, there are some dependencies that are produced while // querying: // The type/size of the value, the score, and if it has an index or not. // So you could run more than one query on the same volume, but it might return // wrong values when it runs concurrently on another volume. // That's not an issue right now, because we run single-threaded and don't use // queries more than once. class Equation : public Term { public: Equation(char **expr); virtual ~Equation(); virtual status_t InitCheck(); status_t ParseQuotedString(char **_start, char **_end); char *CopyString(char *start, char *end); virtual status_t Match(Entry *entry, Node* node, const char *attribute = NULL, int32 type = 0, const uint8 *key = NULL, size_t size = 0); virtual void Complement(); status_t PrepareQuery(Volume *volume, IndexWrapper &index, IndexIterator **iterator, bool queryNonIndexed); status_t GetNextMatching(Volume *volume, IndexIterator *iterator, struct dirent *dirent, size_t bufferSize); virtual void CalculateScore(IndexWrapper &index); virtual int32 Score() const { return fScore; } virtual bool NeedsEntry(); #if DEBUG virtual void PrintToStream(); #endif private: Equation(const Equation &); Equation &operator=(const Equation &); // no implementation status_t ConvertValue(type_code type); bool CompareTo(const uint8 *value, uint16 size); uint8 *Value() const { return (uint8 *)&fValue; } status_t MatchEmptyString(); char *fAttribute; char *fString; union value fValue; type_code fType; size_t fSize; bool fIsPattern; int32 fScore; bool fHasIndex; }; class Operator : public Term { public: Operator(Term *,int8,Term *); virtual ~Operator(); Term *Left() const { return fLeft; } Term *Right() const { return fRight; } virtual status_t Match(Entry *entry, Node* node, const char *attribute = NULL, int32 type = 0, const uint8 *key = NULL, size_t size = 0); virtual void Complement(); virtual void CalculateScore(IndexWrapper &index); virtual int32 Score() const; virtual status_t InitCheck(); virtual bool NeedsEntry(); //Term *Copy() const; #if DEBUG virtual void PrintToStream(); #endif private: Operator(const Operator &); Operator &operator=(const Operator &); // no implementation Term *fLeft,*fRight; }; //--------------------------------- void skipWhitespace(char **expr, int32 skip = 0) { char *string = (*expr) + skip; while (*string == ' ' || *string == '\t') string++; *expr = string; } void skipWhitespaceReverse(char **expr,char *stop) { char *string = *expr; while (string > stop && (*string == ' ' || *string == '\t')) string--; *expr = string; } // #pragma mark - uint32 utf8ToUnicode(char **string) { uint8 *bytes = (uint8 *)*string; int32 length; uint8 mask = 0x1f; switch (bytes[0] & 0xf0) { case 0xc0: case 0xd0: length = 2; break; case 0xe0: length = 3; break; case 0xf0: mask = 0x0f; length = 4; break; default: // valid 1-byte character // and invalid characters (*string)++; return bytes[0]; } uint32 c = bytes[0] & mask; int32 i = 1; for (;i < length && (bytes[i] & 0x80) > 0;i++) c = (c << 6) | (bytes[i] & 0x3f); if (i < length) { // invalid character (*string)++; return (uint32)bytes[0]; } *string += length; return c; } int32 getFirstPatternSymbol(char *string) { char c; for (int32 index = 0;(c = *string++);index++) { if (c == '*' || c == '?' || c == '[') return index; } return -1; } bool isPattern(char *string) { return getFirstPatternSymbol(string) >= 0 ? true : false; } status_t isValidPattern(char *pattern) { while (*pattern) { switch (*pattern++) { case '\\': // the escape character must not be at the end of the pattern if (!*pattern++) return PATTERN_INVALID_ESCAPE; break; case '[': if (pattern[0] == ']' || !pattern[0]) return PATTERN_INVALID_SET; while (*pattern != ']') { if (*pattern == '\\' && !*++pattern) return PATTERN_INVALID_ESCAPE; if (!*pattern) return PATTERN_INVALID_SET; if (pattern[0] == '-' && pattern[1] == '-') return PATTERN_INVALID_RANGE; pattern++; } break; } } return B_OK; } /** Matches the string against the given wildcard pattern. * Returns either MATCH_OK, or NO_MATCH when everything went fine, * or values < 0 (see enum at the top of Query.cpp) if an error * occurs */ status_t matchString(char *pattern, char *string) { while (*pattern) { // end of string == valid end of pattern? if (!string[0]) { while (pattern[0] == '*') pattern++; return !pattern[0] ? MATCH_OK : NO_MATCH; } switch (*pattern++) { case '?': { // match exactly one UTF-8 character; we are // not interested in the result utf8ToUnicode(&string); break; } case '*': { // compact pattern while (true) { if (pattern[0] == '?') { if (!*++string) return NO_MATCH; } else if (pattern[0] != '*') break; pattern++; } // if the pattern is done, we have matched the string if (!pattern[0]) return MATCH_OK; while(true) { // we have removed all occurences of '*' and '?' if (pattern[0] == string[0] || pattern[0] == '[' || pattern[0] == '\\') { status_t status = matchString(pattern,string); if (status < B_OK || status == MATCH_OK) return status; } // we could be nice here and just jump to the next // UTF-8 character - but we wouldn't gain that much // and it'd be slower (since we're checking for // equality before entering the recursion) if (!*++string) return NO_MATCH; } break; } case '[': { bool invert = false; if (pattern[0] == '^' || pattern[0] == '!') { invert = true; pattern++; } if (!pattern[0] || pattern[0] == ']') return MATCH_BAD_PATTERN; uint32 c = utf8ToUnicode(&string); bool matched = false; while (pattern[0] != ']') { if (!pattern[0]) return MATCH_BAD_PATTERN; if (pattern[0] == '\\') pattern++; uint32 first = utf8ToUnicode(&pattern); // Does this character match, or is this a range? if (first == c) { matched = true; break; } else if (pattern[0] == '-' && pattern[1] != ']' && pattern[1]) { pattern++; if (pattern[0] == '\\') { pattern++; if (!pattern[0]) return MATCH_BAD_PATTERN; } uint32 last = utf8ToUnicode(&pattern); if (c >= first && c <= last) { matched = true; break; } } } if (invert) matched = !matched; if (matched) { while (pattern[0] != ']') { if (!pattern[0]) return MATCH_BAD_PATTERN; pattern++; } pattern++; break; } return NO_MATCH; } case '\\': if (!pattern[0]) return MATCH_BAD_PATTERN; // supposed to fall through default: if (pattern[-1] != string[0]) return NO_MATCH; string++; break; } } if (string[0]) return NO_MATCH; return MATCH_OK; } // #pragma mark - Equation::Equation(char **expr) : Term(OP_EQUATION), fAttribute(NULL), fString(NULL), fType(0), fIsPattern(false) { char *string = *expr; char *start = string; char *end = NULL; // Since the equation is the integral part of any query, we're just parsing // the whole thing here. // The whitespace at the start is already removed in Expression::ParseEquation() if (*start == '"' || *start == '\'') { // string is quoted (start has to be on the beginning of a string) if (ParseQuotedString(&start, &end) < B_OK) return; // set string to a valid start of the equation symbol string = end + 2; skipWhitespace(&string); if (*string != '=' && *string != '<' && *string != '>' && *string != '!') { *expr = string; return; } } else { // search the (in)equation for the actual equation symbol (and for other operators // in case the equation is malformed) while (*string && *string != '=' && *string != '<' && *string != '>' && *string != '!' && *string != '&' && *string != '|') string++; // get the attribute string (and trim whitespace), in case // the string was not quoted end = string - 1; skipWhitespaceReverse(&end, start); } // attribute string is empty (which is not allowed) if (start > end) return; // at this point, "start" points to the beginning of the string, "end" points // to the last character of the string, and "string" points to the first // character of the equation symbol // test for the right symbol (as this doesn't need any memory) switch (*string) { case '=': fOp = OP_EQUAL; break; case '>': fOp = *(string + 1) == '=' ? OP_GREATER_THAN_OR_EQUAL : OP_GREATER_THAN; break; case '<': fOp = *(string + 1) == '=' ? OP_LESS_THAN_OR_EQUAL : OP_LESS_THAN; break; case '!': if (*(string + 1) != '=') return; fOp = OP_UNEQUAL; break; // any invalid characters will be rejected default: *expr = string; return; } // lets change "start" to point to the first character after the symbol if (*(string + 1) == '=') string++; string++; skipWhitespace(&string); // allocate & copy the attribute string fAttribute = CopyString(start, end); if (fAttribute == NULL) return; start = string; if (*start == '"' || *start == '\'') { // string is quoted (start has to be on the beginning of a string) if (ParseQuotedString(&start, &end) < B_OK) return; string = end + 2; skipWhitespace(&string); } else { while (*string && *string != '&' && *string != '|' && *string != ')') string++; end = string - 1; skipWhitespaceReverse(&end, start); } // at this point, "start" will point to the first character of the value, // "end" will point to its last character, and "start" to the first non- // whitespace character after the value string fString = CopyString(start, end); if (fString == NULL) return; // patterns are only allowed for these operations (and strings) if (fOp == OP_EQUAL || fOp == OP_UNEQUAL) { fIsPattern = isPattern(fString); if (fIsPattern && isValidPattern(fString) < B_OK) { // we only want to have valid patterns; setting fString // to NULL will cause InitCheck() to fail free(fString); fString = NULL; } } *expr = string; } Equation::~Equation() { if (fAttribute != NULL) free(fAttribute); if (fString != NULL) free(fString); } status_t Equation::InitCheck() { if (fAttribute == NULL || fString == NULL || fOp == OP_NONE) return B_BAD_VALUE; return B_OK; } status_t Equation::ParseQuotedString(char **_start, char **_end) { char *start = *_start; char quote = *start++; char *end = start; for (;*end && *end != quote;end++) { if (*end == '\\') end++; } if (*end == '\0') return B_BAD_VALUE; *_start = start; *_end = end - 1; return B_OK; } char * Equation::CopyString(char *start, char *end) { // end points to the last character of the string - and the length // also has to include the null-termination int32 length = end + 2 - start; // just to make sure; since that's the max. attribute name length and // the max. string in an index, it make sense to have it that way if (length > (int32)kMaxIndexKeyLength || length <= 0) return NULL; char *copy = (char *)malloc(length); if (copy == NULL) return NULL; memcpy(copy,start,length - 1); copy[length - 1] = '\0'; return copy; } status_t Equation::ConvertValue(type_code type) { // Has the type already been converted? if (type == fType) return B_OK; char *string = fString; switch (type) { case B_MIME_STRING_TYPE: type = B_STRING_TYPE; // supposed to fall through case B_STRING_TYPE: strncpy(fValue.CString, string, kMaxIndexKeyLength); fValue.CString[kMaxIndexKeyLength - 1] = '\0'; fSize = strlen(fValue.CString); break; case B_INT32_TYPE: fValue.Int32 = strtol(string, &string, 0); fSize = sizeof(int32); break; case B_UINT32_TYPE: fValue.Int32 = strtoul(string, &string, 0); fSize = sizeof(uint32); break; case B_INT64_TYPE: fValue.Int64 = strtoll(string, &string, 0); fSize = sizeof(int64); break; case B_UINT64_TYPE: fValue.Uint64 = strtoull(string, &string, 0); fSize = sizeof(uint64); break; case B_FLOAT_TYPE: fValue.Float = strtod(string, &string); fSize = sizeof(float); break; case B_DOUBLE_TYPE: fValue.Double = strtod(string, &string); fSize = sizeof(double); break; default: FATAL("query value conversion to 0x%" B_PRIx32 " requested!\n", type); // should we fail here or just do a safety int32 conversion? return B_ERROR; } fType = type; // patterns are only allowed for string types if (fType != B_STRING_TYPE && fIsPattern) fIsPattern = false; return B_OK; } /** Returns true when the key matches the equation. You have to * call ConvertValue() before this one. */ bool Equation::CompareTo(const uint8 *value, uint16 size) { int32 compare; // fIsPattern is only true if it's a string type, and fOp OP_EQUAL, or OP_UNEQUAL if (fIsPattern) { // we have already validated the pattern, so we don't check for failing // here - if something is broken, and matchString() returns an error, // we just don't match compare = matchString(fValue.CString, (char *)value) == MATCH_OK ? 0 : 1; } else compare = compareKeys(fType, value, size, Value(), fSize); switch (fOp) { case OP_EQUAL: return compare == 0; case OP_UNEQUAL: return compare != 0; case OP_LESS_THAN: return compare < 0; case OP_LESS_THAN_OR_EQUAL: return compare <= 0; case OP_GREATER_THAN: return compare > 0; case OP_GREATER_THAN_OR_EQUAL: return compare >= 0; } FATAL("Unknown/Unsupported operation: %d\n", fOp); return false; } void Equation::Complement() { D(if (fOp <= OP_EQUATION || fOp > OP_LESS_THAN_OR_EQUAL) { FATAL("op out of range!"); return; }); int8 complementOp[] = {OP_UNEQUAL, OP_EQUAL, OP_LESS_THAN_OR_EQUAL, OP_GREATER_THAN_OR_EQUAL, OP_LESS_THAN, OP_GREATER_THAN}; fOp = complementOp[fOp - OP_EQUAL]; } status_t Equation::MatchEmptyString() { // there is no matching attribute, we will just bail out if we // already know that our value is not of a string type. // If not, it will be converted to a string - and then be compared with "". // That's why we have to call ConvertValue() here - but it will be // a cheap call for the next time // Should we do this only for OP_UNEQUAL? if (fType != 0 && fType != B_STRING_TYPE) return NO_MATCH; status_t status = ConvertValue(B_STRING_TYPE); if (status == B_OK) status = CompareTo((const uint8 *)"", fSize) ? MATCH_OK : NO_MATCH; return status; } /** Matches the inode's attribute value with the equation. * Returns MATCH_OK if it matches, NO_MATCH if not, < 0 if something went wrong */ status_t Equation::Match(Entry *entry, Node* node, const char *attributeName, int32 type, const uint8 *key, size_t size) { // get a pointer to the attribute in question union value value; const uint8 *buffer; // first, check if we are matching for a live query and use that value if (attributeName != NULL && !strcmp(fAttribute, attributeName)) { if (key == NULL) { if (type == B_STRING_TYPE) { // special case: a NULL "name" means the entry has been removed // or not yet been added -- we refuse to match, whatever the // pattern if (!strcmp(fAttribute, "name")) return NO_MATCH; return MatchEmptyString(); } return NO_MATCH; } buffer = const_cast(key); } else if (!strcmp(fAttribute, "name")) { // if not, check for "fake" attributes, "name", "size", "last_modified", if (!entry) return B_ERROR; buffer = (uint8 *)entry->GetName(); if (buffer == NULL) return B_ERROR; type = B_STRING_TYPE; size = strlen((const char *)buffer); } else if (!strcmp(fAttribute,"size")) { value.Int64 = node->GetSize(); buffer = (uint8 *)&value; type = B_INT64_TYPE; } else if (!strcmp(fAttribute,"last_modified")) { value.Int32 = node->GetMTime(); buffer = (uint8 *)&value; type = B_INT32_TYPE; } else { // then for attributes Attribute *attribute = NULL; buffer = (const uint8*)alloca(kMaxIndexKeyLength); if (node->FindAttribute(fAttribute, &attribute) == B_OK) { attribute->GetKey((uint8*)buffer, &size); type = attribute->GetType(); } else return MatchEmptyString(); } // prepare own value for use, if it is possible to convert it status_t status = ConvertValue(type); if (status == B_OK) status = CompareTo(buffer, size) ? MATCH_OK : NO_MATCH; RETURN_ERROR(status); } void Equation::CalculateScore(IndexWrapper &index) { // As always, these values could be tuned and refined. // And the code could also need some real world testing :-) // do we have to operate on a "foreign" index? if (fOp == OP_UNEQUAL || index.SetTo(fAttribute) < B_OK) { fScore = 0; return; } // if we have a pattern, how much does it help our search? if (fIsPattern) fScore = getFirstPatternSymbol(fString) << 3; else { // Score by operator if (fOp == OP_EQUAL) // higher than pattern="255 chars+*" fScore = 2048; else // the pattern search is regarded cheaper when you have at // least one character to set your index to fScore = 5; } // take index size into account (1024 is the current node size // in our B+trees) // 2048 * 2048 == 4194304 is the maximum score (for an empty // tree, since the header + 1 node are already 2048 bytes) fScore = fScore * ((2048 * 1024LL) / index.GetSize()); } status_t Equation::PrepareQuery(Volume */*volume*/, IndexWrapper &index, IndexIterator **iterator, bool queryNonIndexed) { status_t status = index.SetTo(fAttribute); // if we should query attributes without an index, we can just proceed here if (status < B_OK && !queryNonIndexed) return B_ENTRY_NOT_FOUND; type_code type; // special case for OP_UNEQUAL - it will always operate through the whole index // but we need the call to the original index to get the correct type if (status < B_OK || fOp == OP_UNEQUAL) { // Try to get an index that holds all files (name) // Also sets the default type for all attributes without index // to string. type = status < B_OK ? B_STRING_TYPE : index.Type(); if (index.SetTo("name") < B_OK) return B_ENTRY_NOT_FOUND; fHasIndex = false; } else { fHasIndex = true; type = index.Type(); } if (ConvertValue(type) < B_OK) return B_BAD_VALUE; *iterator = new IndexIterator(&index); if (*iterator == NULL) return B_NO_MEMORY; if ((fOp == OP_EQUAL || fOp == OP_GREATER_THAN || fOp == OP_GREATER_THAN_OR_EQUAL || fIsPattern) && fHasIndex) { // set iterator to the exact position int32 keySize = index.KeySize(); // at this point, fIsPattern is only true if it's a string type, and fOp // is either OP_EQUAL or OP_UNEQUAL if (fIsPattern) { // let's see if we can use the beginning of the key for positioning // the iterator and adjust the key size; if not, just leave the // iterator at the start and return success keySize = getFirstPatternSymbol(fString); if (keySize <= 0) return B_OK; } if (keySize == 0) { // B_STRING_TYPE doesn't have a fixed length, so it was set // to 0 before - we compute the correct value here if (fType == B_STRING_TYPE) { keySize = strlen(fValue.CString); // The empty string is a special case - we normally don't check // for the trailing null byte, in the case for the empty string // we do it explicitly, because there can't be keys in the B+tree // with a length of zero if (keySize == 0) keySize = 1; } else RETURN_ERROR(B_ENTRY_NOT_FOUND); } status = (*iterator)->Find(Value(), keySize); if (fOp == OP_EQUAL && !fIsPattern) return status; else if (status == B_ENTRY_NOT_FOUND && (fIsPattern || fOp == OP_GREATER_THAN || fOp == OP_GREATER_THAN_OR_EQUAL)) { return (*iterator)->Rewind(); } RETURN_ERROR(status); } return B_OK; } status_t Equation::GetNextMatching(Volume *volume, IndexIterator *iterator, struct dirent *dirent, size_t bufferSize) { while (true) { union value indexValue; uint16 keyLength; Entry *entry = NULL; status_t status = iterator->GetNextEntry((uint8*)&indexValue, &keyLength, (uint16)sizeof(indexValue), &entry); if (status < B_OK) return status; // only compare against the index entry when this is the correct // index for the equation if (fHasIndex && !CompareTo((uint8 *)&indexValue, keyLength)) { // They aren't equal? let the operation decide what to do // Since we always start at the beginning of the index (or the correct // position), only some needs to be stopped if the entry doesn't fit. if (fOp == OP_LESS_THAN || fOp == OP_LESS_THAN_OR_EQUAL || (fOp == OP_EQUAL && !fIsPattern)) return B_ENTRY_NOT_FOUND; continue; } // ToDo: check user permissions here - but which one?! // we could filter out all those where we don't have // read access... (we should check for every parent // directory if the X_OK is allowed) // Although it's quite expensive to open all parents, // it's likely that the application that runs the // query will do something similar (and we don't have // to do it for root, either). // go up in the tree until a &&-operator is found, and check if the // inode matches with the rest of the expression - we don't have to // check ||-operators for that Term *term = this; status = MATCH_OK; if (!fHasIndex) status = Match(entry, entry->GetNode()); while (term != NULL && status == MATCH_OK) { Operator *parent = (Operator *)term->Parent(); if (parent == NULL) break; if (parent->Op() == OP_AND) { // choose the other child of the parent Term *other = parent->Right(); if (other == term) other = parent->Left(); if (other == NULL) { FATAL("&&-operator has only one child... (parent = %p)\n", parent); break; } status = other->Match(entry, entry->GetNode()); if (status < 0) { REPORT_ERROR(status); status = NO_MATCH; } } term = (Term *)parent; } if (status == MATCH_OK) { size_t nameLen = strlen(entry->GetName()); // check, whether the entry fits into the buffer, // and fill it in size_t length = (dirent->d_name + nameLen + 1) - (char*)dirent; if (length > bufferSize) RETURN_ERROR(B_BUFFER_OVERFLOW); dirent->d_dev = volume->GetID(); dirent->d_ino = entry->GetNode()->GetID(); dirent->d_pdev = volume->GetID(); dirent->d_pino = entry->GetParent()->GetID(); memcpy(dirent->d_name, entry->GetName(), nameLen); dirent->d_name[nameLen] = '\0'; dirent->d_reclen = length; } if (status == MATCH_OK) return B_OK; } RETURN_ERROR(B_ERROR); } bool Equation::NeedsEntry() { return strcmp(fAttribute, "name") == 0; } // #pragma mark - Operator::Operator(Term *left, int8 op, Term *right) : Term(op), fLeft(left), fRight(right) { if (left) left->SetParent(this); if (right) right->SetParent(this); } Operator::~Operator() { delete fLeft; delete fRight; } status_t Operator::Match(Entry *entry, Node* node, const char *attribute, int32 type, const uint8 *key, size_t size) { if (fOp == OP_AND) { status_t status = fLeft->Match(entry, node, attribute, type, key, size); if (status != MATCH_OK) return status; return fRight->Match(entry, node, attribute, type, key, size); } else { // choose the term with the better score for OP_OR if (fRight->Score() > fLeft->Score()) { status_t status = fRight->Match(entry, node, attribute, type, key, size); if (status != NO_MATCH) return status; } return fLeft->Match(entry, node, attribute, type, key, size); } } void Operator::Complement() { if (fOp == OP_AND) fOp = OP_OR; else fOp = OP_AND; fLeft->Complement(); fRight->Complement(); } void Operator::CalculateScore(IndexWrapper &index) { fLeft->CalculateScore(index); fRight->CalculateScore(index); } int32 Operator::Score() const { if (fOp == OP_AND) { // return the one with the better score if (fRight->Score() > fLeft->Score()) return fRight->Score(); return fLeft->Score(); } // for OP_OR, be honest, and return the one with the worse score if (fRight->Score() < fLeft->Score()) return fRight->Score(); return fLeft->Score(); } status_t Operator::InitCheck() { if ((fOp != OP_AND && fOp != OP_OR) || fLeft == NULL || fLeft->InitCheck() < B_OK || fRight == NULL || fRight->InitCheck() < B_OK) return B_ERROR; return B_OK; } bool Operator::NeedsEntry() { return ((fLeft && fLeft->NeedsEntry()) || (fRight && fRight->NeedsEntry())); } #if 0 Term * Operator::Copy() const { if (fEquation != NULL) { Equation *equation = new Equation(*fEquation); if (equation == NULL) return NULL; Term *term = new Term(equation); if (term == NULL) delete equation; return term; } Term *left = NULL, *right = NULL; if (fLeft != NULL && (left = fLeft->Copy()) == NULL) return NULL; if (fRight != NULL && (right = fRight->Copy()) == NULL) { delete left; return NULL; } Term *term = new Term(left,fOp,right); if (term == NULL) { delete left; delete right; return NULL; } return term; } #endif // #pragma mark - #if DEBUG void Operator::PrintToStream() { D(__out("( ")); if (fLeft != NULL) fLeft->PrintToStream(); const char* op; switch (fOp) { case OP_OR: op = "OR"; break; case OP_AND: op = "AND"; break; default: op = "?"; break; } D(__out(" %s ", op)); if (fRight != NULL) fRight->PrintToStream(); D(__out(" )")); } void Equation::PrintToStream() { const char* op; switch (fOp) { case OP_EQUAL: op = "=="; break; case OP_UNEQUAL: op = "!="; break; case OP_GREATER_THAN: op = ">"; break; case OP_GREATER_THAN_OR_EQUAL: op = ">="; break; case OP_LESS_THAN: op = "<"; break; case OP_LESS_THAN_OR_EQUAL: op = "<="; break; default: op = "???"; break; } D(__out("[\"%s\" %s \"%s\"]", fAttribute, op, fString)); } #endif /* DEBUG */ // #pragma mark - Expression::Expression(char *expr) { if (expr == NULL) return; fTerm = ParseOr(&expr); if (fTerm != NULL && fTerm->InitCheck() < B_OK) { FATAL("Corrupt tree in expression!\n"); delete fTerm; fTerm = NULL; } D(if (fTerm != NULL) { fTerm->PrintToStream(); D(__out("\n")); if (*expr != '\0') PRINT("Unexpected end of string: \"%s\"!\n", expr); }); fPosition = expr; } Expression::~Expression() { delete fTerm; } Term * Expression::ParseEquation(char **expr) { skipWhitespace(expr); bool nott = false; // note: not is a C++ keyword if (**expr == '!') { skipWhitespace(expr, 1); if (**expr != '(') return NULL; nott = true; } if (**expr == ')') { // shouldn't be handled here return NULL; } else if (**expr == '(') { skipWhitespace(expr, 1); Term *term = ParseOr(expr); skipWhitespace(expr); if (**expr != ')') { delete term; return NULL; } // If the term is negated, we just complement the tree, to get // rid of the not, a.k.a. DeMorgan's Law. if (nott) term->Complement(); skipWhitespace(expr, 1); return term; } Equation *equation = new Equation(expr); if (equation == NULL || equation->InitCheck() < B_OK) { delete equation; return NULL; } return equation; } Term * Expression::ParseAnd(char **expr) { Term *left = ParseEquation(expr); if (left == NULL) return NULL; while (IsOperator(expr,'&')) { Term *right = ParseAnd(expr); Term *newParent = NULL; if (right == NULL || (newParent = new Operator(left, OP_AND, right)) == NULL) { delete left; delete right; return NULL; } left = newParent; } return left; } Term * Expression::ParseOr(char **expr) { Term *left = ParseAnd(expr); if (left == NULL) return NULL; while (IsOperator(expr,'|')) { Term *right = ParseAnd(expr); Term *newParent = NULL; if (right == NULL || (newParent = new Operator(left, OP_OR, right)) == NULL) { delete left; delete right; return NULL; } left = newParent; } return left; } bool Expression::IsOperator(char **expr, char op) { char *string = *expr; if (*string == op && *(string + 1) == op) { *expr += 2; return true; } return false; } status_t Expression::InitCheck() { if (fTerm == NULL) return B_BAD_VALUE; return B_OK; } // #pragma mark - Query::Query(Volume *volume, Expression *expression, uint32 flags) : fVolume(volume), fExpression(expression), fCurrent(NULL), fIterator(NULL), fIndex(volume), fFlags(flags), fPort(-1), fNeedsEntry(false) { // if the expression has a valid root pointer, the whole tree has // already passed the sanity check, so that we don't have to check // every pointer if (volume == NULL || expression == NULL || expression->Root() == NULL) return; // create index on the stack and delete it afterwards fExpression->Root()->CalculateScore(fIndex); fIndex.Unset(); fNeedsEntry = fExpression->Root()->NeedsEntry(); Rewind(); if (fFlags & B_LIVE_QUERY) volume->AddQuery(this); } Query::~Query() { if (fFlags & B_LIVE_QUERY) fVolume->RemoveQuery(this); } status_t Query::Rewind() { // free previous stuff fStack.MakeEmpty(); delete fIterator; fIterator = NULL; fCurrent = NULL; // put the whole expression on the stack Stack stack; stack.Push(fExpression->Root()); Term *term; while (stack.Pop(&term)) { if (term->Op() < OP_EQUATION) { Operator *op = (Operator *)term; if (op->Op() == OP_OR) { stack.Push(op->Left()); stack.Push(op->Right()); } else { // For OP_AND, we can use the scoring system to decide which path to add if (op->Right()->Score() > op->Left()->Score()) stack.Push(op->Right()); else stack.Push(op->Left()); } } else if (term->Op() == OP_EQUATION || fStack.Push((Equation *)term) < B_OK) FATAL("Unknown term on stack or stack error"); } return B_OK; } status_t Query::GetNextEntry(struct dirent *dirent, size_t size) { // If we don't have an equation to use yet/anymore, get a new one // from the stack while (true) { if (fIterator == NULL) { if (!fStack.Pop(&fCurrent) || fCurrent == NULL || fCurrent->PrepareQuery(fVolume, fIndex, &fIterator, fFlags & B_QUERY_NON_INDEXED) < B_OK) return B_ENTRY_NOT_FOUND; } if (fCurrent == NULL) RETURN_ERROR(B_ERROR); status_t status = fCurrent->GetNextMatching(fVolume, fIterator, dirent, size); if (status < B_OK) { delete fIterator; fIterator = NULL; fCurrent = NULL; } else { // only return if we have another entry return B_OK; } } } void Query::SetLiveMode(port_id port, int32 token) { fPort = port; fToken = token; if ((fFlags & B_LIVE_QUERY) == 0) { // you can decide at any point to set the live query mode, // only live queries have to be updated by attribute changes fFlags |= B_LIVE_QUERY; fVolume->AddQuery(this); } } static void send_entry_notification(port_id port, int32 token, Volume* volume, Entry* entry, bool created) { if (created) { notify_query_entry_created(port, token, volume->GetID(), entry->GetParent()->GetID(), entry->GetName(), entry->GetNode()->GetID()); } else { notify_query_entry_removed(port, token, volume->GetID(), entry->GetParent()->GetID(), entry->GetName(), entry->GetNode()->GetID()); } } void Query::LiveUpdate(Entry *entry, Node* node, const char *attribute, int32 type, const uint8 *oldKey, size_t oldLength, const uint8 *newKey, size_t newLength) { PRINT("%p->Query::LiveUpdate(%p, %p, \"%s\", 0x%lx, %p, %lu, %p, %lu)\n", this, entry, node, attribute, type, oldKey, oldLength, newKey, newLength); if (fPort < 0 || fExpression == NULL || node == NULL || attribute == NULL) return; // ToDo: check if the attribute is part of the query at all... // If no entry has been supplied, but the we need one for the evaluation // (i.e. the "name" attribute is used), we invoke ourselves for all entries // referring to the given node. if (!entry && fNeedsEntry) { entry = node->GetFirstReferrer(); while (entry) { LiveUpdate(entry, node, attribute, type, oldKey, oldLength, newKey, newLength); entry = node->GetNextReferrer(entry); } return; } status_t oldStatus = fExpression->Root()->Match(entry, node, attribute, type, oldKey, oldLength); status_t newStatus = fExpression->Root()->Match(entry, node, attribute, type, newKey, newLength); PRINT(" oldStatus: 0x%lx, newStatus: 0x%lx\n", oldStatus, newStatus); bool created; if (oldStatus == MATCH_OK && newStatus == MATCH_OK) { // only send out a notification if the name was changed if (oldKey == NULL || strcmp(attribute,"name")) return; if (entry) { // entry should actually always be given, when the changed // attribute is the entry name PRINT("notification: old: removed\n"); notify_query_entry_removed(fPort, fToken, fVolume->GetID(), entry->GetParent()->GetID(), (const char *)oldKey, entry->GetNode()->GetID()); } created = true; } else if (oldStatus != MATCH_OK && newStatus != MATCH_OK) { // nothing has changed return; } else if (oldStatus == MATCH_OK && newStatus != MATCH_OK) created = false; else created = true; // We send a notification for the given entry, if any, or otherwise for // all entries referring to the node; if (entry) { PRINT("notification: new: %s\n", (created ? "created" : "removed")); send_entry_notification(fPort, fToken, fVolume, entry, created); } else { entry = node->GetFirstReferrer(); while (entry) { send_entry_notification(fPort, fToken, fVolume, entry, created); entry = node->GetNextReferrer(entry); } } }