*
* 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.
*/
#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 <SupportDefs.h>
#include <TypeConstants.h>
#include <AppDefs.h>
#include <fs_query.h>
#include <malloc.h>
#include <stdio.h>
#include <string.h>
IndexWrapper::IndexWrapper(Volume *volume)
: fVolume(volume),
fIndex(NULL)
{
}
status_t
IndexWrapper::SetTo(const char *name)
{
fIndex = NULL;
if (fVolume)
fIndex = fVolume->FindIndex(name);
return (fIndex ? B_OK : B_ENTRY_NOT_FOUND);
}
void
IndexWrapper::Unset()
{
fIndex = NULL;
}
uint32
IndexWrapper::Type() const
{
return (fIndex ? fIndex->GetType() : 0);
}
off_t
IndexWrapper::GetSize() const
{
return 1024LL + (fIndex ? fIndex->CountEntries() : 0) * 16LL;
}
int32
IndexWrapper::KeySize() const
{
return (fIndex ? fIndex->GetKeyLength() : 0);
}
IndexIterator::IndexIterator(IndexWrapper *indexWrapper)
: fIndexWrapper(indexWrapper),
fIterator(),
fInitialized(false)
{
}
status_t
IndexIterator::Find(const uint8 *const key, size_t keyLength)
{
status_t error = B_ENTRY_NOT_FOUND;
if (fIndexWrapper && fIndexWrapper->fIndex) {
fInitialized = fIndexWrapper->fIndex->Find(key, keyLength, &fIterator);
if (fInitialized)
error = B_OK;
}
return error;
}
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;
}
status_t
IndexIterator::GetNextEntry(uint8 *buffer, uint16 *_keyLength,
size_t , Entry **_entry)
{
status_t error = B_ENTRY_NOT_FOUND;
if (fIndexWrapper && fIndexWrapper->fIndex) {
if (!fInitialized) {
fIndexWrapper->fIndex->GetIterator(&fIterator);
fInitialized = true;
}
size_t keyLength;
if (Entry *entry = fIterator.GetCurrent(buffer, &keyLength)) {
*_keyLength = keyLength;
*_entry = entry;
error = B_OK;
}
fIterator.GetNext();
}
return error;
}
template<typename Key>
static inline
int
compare_integral(const Key &a, const Key &b)
{
if (a < b)
return -1;
else if (a > b)
return 1;
return 0;
}
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;
}
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);
}
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
};
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];
};
#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;
};
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 &);
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();
#if DEBUG
virtual void PrintToStream();
#endif
private:
Operator(const Operator &);
Operator &operator=(const Operator &);
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;
}
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:
(*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) {
(*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 '\\':
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;
}
* 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) {
if (!string[0]) {
while (pattern[0] == '*')
pattern++;
return !pattern[0] ? MATCH_OK : NO_MATCH;
}
switch (*pattern++) {
case '?':
{
utf8ToUnicode(&string);
break;
}
case '*':
{
while (true) {
if (pattern[0] == '?') {
if (!*++string)
return NO_MATCH;
} else if (pattern[0] != '*')
break;
pattern++;
}
if (!pattern[0])
return MATCH_OK;
while(true) {
if (pattern[0] == string[0]
|| pattern[0] == '['
|| pattern[0] == '\\') {
status_t status = matchString(pattern,string);
if (status < B_OK || status == MATCH_OK)
return status;
}
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);
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;
default:
if (pattern[-1] != string[0])
return NO_MATCH;
string++;
break;
}
}
if (string[0])
return NO_MATCH;
return MATCH_OK;
}
Equation::Equation(char **expr)
: Term(OP_EQUATION),
fAttribute(NULL),
fString(NULL),
fType(0),
fIsPattern(false)
{
char *string = *expr;
char *start = string;
char *end = NULL;
if (*start == '"' || *start == '\'') {
if (ParseQuotedString(&start, &end) < B_OK)
return;
string = end + 2;
skipWhitespace(&string);
if (*string != '=' && *string != '<' && *string != '>' && *string != '!') {
*expr = string;
return;
}
} else {
while (*string && *string != '=' && *string != '<' && *string != '>' && *string != '!'
&& *string != '&' && *string != '|')
string++;
end = string - 1;
skipWhitespaceReverse(&end, start);
}
if (start > end)
return;
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;
default:
*expr = string;
return;
}
if (*(string + 1) == '=')
string++;
string++;
skipWhitespace(&string);
fAttribute = CopyString(start, end);
if (fAttribute == NULL)
return;
start = string;
if (*start == '"' || *start == '\'') {
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);
}
fString = CopyString(start, end);
if (fString == NULL)
return;
if (fOp == OP_EQUAL || fOp == OP_UNEQUAL) {
fIsPattern = isPattern(fString);
if (fIsPattern && isValidPattern(fString) < B_OK) {
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)
{
int32 length = end + 2 - start;
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)
{
if (type == fType)
return B_OK;
char *string = fString;
switch (type) {
case B_MIME_STRING_TYPE:
type = B_STRING_TYPE;
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);
return B_ERROR;
}
fType = type;
if (fType != B_STRING_TYPE && fIsPattern)
fIsPattern = false;
return B_OK;
}
* call ConvertValue() before this one.
*/
bool
Equation::CompareTo(const uint8 *value, uint16 size)
{
int32 compare;
if (fIsPattern) {
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()
{
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;
}
* 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)
{
union value value;
const uint8 *buffer;
if (attributeName != NULL && !strcmp(fAttribute, attributeName)) {
if (key == NULL) {
if (type == B_STRING_TYPE) {
if (!strcmp(fAttribute, "name"))
return NO_MATCH;
return MatchEmptyString();
}
return NO_MATCH;
}
buffer = const_cast<uint8 *>(key);
} else if (!strcmp(fAttribute, "name")) {
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 {
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();
}
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)
{
if (fOp == OP_UNEQUAL || index.SetTo(fAttribute) < B_OK) {
fScore = 0;
return;
}
if (fIsPattern)
fScore = getFirstPatternSymbol(fString) << 3;
else {
if (fOp == OP_EQUAL)
fScore = 2048;
else
fScore = 5;
}
fScore = fScore * ((2048 * 1024LL) / index.GetSize());
}
status_t
Equation::PrepareQuery(Volume *, IndexWrapper &index, IndexIterator **iterator, bool queryNonIndexed)
{
status_t status = index.SetTo(fAttribute);
if (status < B_OK && !queryNonIndexed)
return B_ENTRY_NOT_FOUND;
type_code type;
if (status < B_OK || fOp == OP_UNEQUAL) {
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) {
int32 keySize = index.KeySize();
if (fIsPattern) {
keySize = getFirstPatternSymbol(fString);
if (keySize <= 0)
return B_OK;
}
if (keySize == 0) {
if (fType == B_STRING_TYPE) {
keySize = strlen(fValue.CString);
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;
if (fHasIndex && !CompareTo((uint8 *)&indexValue, keyLength)) {
if (fOp == OP_LESS_THAN
|| fOp == OP_LESS_THAN_OR_EQUAL
|| (fOp == OP_EQUAL && !fIsPattern))
return B_ENTRY_NOT_FOUND;
continue;
}
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) {
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());
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;
}
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 {
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) {
if (fRight->Score() > fLeft->Score())
return fRight->Score();
return fLeft->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
#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
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;
if (**expr == '!') {
skipWhitespace(expr, 1);
if (**expr != '(')
return NULL;
nott = true;
}
if (**expr == ')') {
return NULL;
} else if (**expr == '(') {
skipWhitespace(expr, 1);
Term *term = ParseOr(expr);
skipWhitespace(expr);
if (**expr != ')') {
delete term;
return NULL;
}
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;
}
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 (volume == NULL || expression == NULL || expression->Root() == NULL)
return;
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()
{
fStack.MakeEmpty();
delete fIterator;
fIterator = NULL;
fCurrent = NULL;
Stack<Term *> 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 {
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)
{
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 {
return B_OK;
}
}
}
void
Query::SetLiveMode(port_id port, int32 token)
{
fPort = port;
fToken = token;
if ((fFlags & B_LIVE_QUERY) == 0) {
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;
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) {
if (oldKey == NULL || strcmp(attribute,"name"))
return;
if (entry) {
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) {
return;
} else if (oldStatus == MATCH_OK && newStatus != MATCH_OK)
created = false;
else
created = true;
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);
}
}
}