You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							253 lines
						
					
					
						
							7.0 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							253 lines
						
					
					
						
							7.0 KiB
						
					
					
				| package policy_engine | |
| 
 | |
| import ( | |
| 	"regexp" | |
| 	"strings" | |
| 	"sync" | |
| 
 | |
| 	"github.com/seaweedfs/seaweedfs/weed/glog" | |
| ) | |
| 
 | |
| // WildcardMatcher provides unified wildcard matching functionality | |
| type WildcardMatcher struct { | |
| 	// Use regex for complex patterns with ? wildcards | |
| 	// Use string manipulation for simple * patterns (better performance) | |
| 	useRegex bool | |
| 	regex    *regexp.Regexp | |
| 	pattern  string | |
| } | |
| 
 | |
| // WildcardMatcherCache provides caching for WildcardMatcher instances | |
| type WildcardMatcherCache struct { | |
| 	mu          sync.RWMutex | |
| 	matchers    map[string]*WildcardMatcher | |
| 	maxSize     int | |
| 	accessOrder []string // For LRU eviction | |
| } | |
| 
 | |
| // NewWildcardMatcherCache creates a new WildcardMatcherCache with a configurable maxSize | |
| func NewWildcardMatcherCache(maxSize int) *WildcardMatcherCache { | |
| 	if maxSize <= 0 { | |
| 		maxSize = 1000 // Default value | |
| 	} | |
| 	return &WildcardMatcherCache{ | |
| 		matchers: make(map[string]*WildcardMatcher), | |
| 		maxSize:  maxSize, | |
| 	} | |
| } | |
| 
 | |
| // Global cache instance | |
| var wildcardMatcherCache = NewWildcardMatcherCache(1000) // Default maxSize | |
|  | |
| // GetCachedWildcardMatcher gets or creates a cached WildcardMatcher for the given pattern | |
| func GetCachedWildcardMatcher(pattern string) (*WildcardMatcher, error) { | |
| 	// Fast path: check if already in cache | |
| 	wildcardMatcherCache.mu.RLock() | |
| 	if matcher, exists := wildcardMatcherCache.matchers[pattern]; exists { | |
| 		wildcardMatcherCache.mu.RUnlock() | |
| 		wildcardMatcherCache.updateAccessOrder(pattern) | |
| 		return matcher, nil | |
| 	} | |
| 	wildcardMatcherCache.mu.RUnlock() | |
| 
 | |
| 	// Slow path: create new matcher and cache it | |
| 	wildcardMatcherCache.mu.Lock() | |
| 	defer wildcardMatcherCache.mu.Unlock() | |
| 
 | |
| 	// Double-check after acquiring write lock | |
| 	if matcher, exists := wildcardMatcherCache.matchers[pattern]; exists { | |
| 		wildcardMatcherCache.updateAccessOrderLocked(pattern) | |
| 		return matcher, nil | |
| 	} | |
| 
 | |
| 	// Create new matcher | |
| 	matcher, err := NewWildcardMatcher(pattern) | |
| 	if err != nil { | |
| 		return nil, err | |
| 	} | |
| 
 | |
| 	// Evict old entries if cache is full | |
| 	if len(wildcardMatcherCache.matchers) >= wildcardMatcherCache.maxSize { | |
| 		wildcardMatcherCache.evictLeastRecentlyUsed() | |
| 	} | |
| 
 | |
| 	// Cache it | |
| 	wildcardMatcherCache.matchers[pattern] = matcher | |
| 	wildcardMatcherCache.accessOrder = append(wildcardMatcherCache.accessOrder, pattern) | |
| 	return matcher, nil | |
| } | |
| 
 | |
| // updateAccessOrder updates the access order for LRU eviction (with read lock) | |
| func (c *WildcardMatcherCache) updateAccessOrder(pattern string) { | |
| 	c.mu.Lock() | |
| 	defer c.mu.Unlock() | |
| 	c.updateAccessOrderLocked(pattern) | |
| } | |
| 
 | |
| // updateAccessOrderLocked updates the access order for LRU eviction (without locking) | |
| func (c *WildcardMatcherCache) updateAccessOrderLocked(pattern string) { | |
| 	// Remove pattern from its current position | |
| 	for i, p := range c.accessOrder { | |
| 		if p == pattern { | |
| 			c.accessOrder = append(c.accessOrder[:i], c.accessOrder[i+1:]...) | |
| 			break | |
| 		} | |
| 	} | |
| 	// Add pattern to the end (most recently used) | |
| 	c.accessOrder = append(c.accessOrder, pattern) | |
| } | |
| 
 | |
| // evictLeastRecentlyUsed removes the least recently used pattern from the cache | |
| func (c *WildcardMatcherCache) evictLeastRecentlyUsed() { | |
| 	if len(c.accessOrder) == 0 { | |
| 		return | |
| 	} | |
| 
 | |
| 	// Remove the least recently used pattern (first in the list) | |
| 	lruPattern := c.accessOrder[0] | |
| 	c.accessOrder = c.accessOrder[1:] | |
| 	delete(c.matchers, lruPattern) | |
| } | |
| 
 | |
| // ClearCache clears all cached patterns (useful for testing) | |
| func (c *WildcardMatcherCache) ClearCache() { | |
| 	c.mu.Lock() | |
| 	defer c.mu.Unlock() | |
| 	c.matchers = make(map[string]*WildcardMatcher) | |
| 	c.accessOrder = c.accessOrder[:0] | |
| } | |
| 
 | |
| // GetCacheStats returns cache statistics | |
| func (c *WildcardMatcherCache) GetCacheStats() (size int, maxSize int) { | |
| 	c.mu.RLock() | |
| 	defer c.mu.RUnlock() | |
| 	return len(c.matchers), c.maxSize | |
| } | |
| 
 | |
| // NewWildcardMatcher creates a new wildcard matcher for the given pattern | |
| func NewWildcardMatcher(pattern string) (*WildcardMatcher, error) { | |
| 	matcher := &WildcardMatcher{ | |
| 		pattern: pattern, | |
| 	} | |
| 
 | |
| 	// Determine if we need regex (contains ? wildcards) | |
| 	if strings.Contains(pattern, "?") { | |
| 		matcher.useRegex = true | |
| 		regex, err := compileWildcardPattern(pattern) | |
| 		if err != nil { | |
| 			return nil, err | |
| 		} | |
| 		matcher.regex = regex | |
| 	} else { | |
| 		matcher.useRegex = false | |
| 	} | |
| 
 | |
| 	return matcher, nil | |
| } | |
| 
 | |
| // Match checks if a string matches the wildcard pattern | |
| func (m *WildcardMatcher) Match(str string) bool { | |
| 	if m.useRegex { | |
| 		return m.regex.MatchString(str) | |
| 	} | |
| 	return matchWildcardString(m.pattern, str) | |
| } | |
| 
 | |
| // MatchesWildcard provides a simple function interface for wildcard matching | |
| // This function consolidates the logic from the previous separate implementations | |
| func MatchesWildcard(pattern, str string) bool { | |
| 	// Handle simple cases first | |
| 	if pattern == "*" { | |
| 		return true | |
| 	} | |
| 	if pattern == str { | |
| 		return true | |
| 	} | |
| 
 | |
| 	// Use regex for patterns with ? wildcards, string manipulation for * only | |
| 	if strings.Contains(pattern, "?") { | |
| 		return matchWildcardRegex(pattern, str) | |
| 	} | |
| 	return matchWildcardString(pattern, str) | |
| } | |
| 
 | |
| // CompileWildcardPattern converts a wildcard pattern to a compiled regex | |
| // This replaces the previous compilePattern function | |
| func CompileWildcardPattern(pattern string) (*regexp.Regexp, error) { | |
| 	return compileWildcardPattern(pattern) | |
| } | |
| 
 | |
| // matchWildcardString uses string manipulation for * wildcards only (more efficient) | |
| func matchWildcardString(pattern, str string) bool { | |
| 	// Handle simple cases | |
| 	if pattern == "*" { | |
| 		return true | |
| 	} | |
| 	if pattern == str { | |
| 		return true | |
| 	} | |
| 
 | |
| 	// Split pattern by wildcards | |
| 	parts := strings.Split(pattern, "*") | |
| 	if len(parts) == 1 { | |
| 		// No wildcards, exact match | |
| 		return pattern == str | |
| 	} | |
| 
 | |
| 	// Check if string starts with first part | |
| 	if len(parts[0]) > 0 && !strings.HasPrefix(str, parts[0]) { | |
| 		return false | |
| 	} | |
| 
 | |
| 	// Check if string ends with last part | |
| 	if len(parts[len(parts)-1]) > 0 && !strings.HasSuffix(str, parts[len(parts)-1]) { | |
| 		return false | |
| 	} | |
| 
 | |
| 	// Check middle parts | |
| 	searchStr := str | |
| 	if len(parts[0]) > 0 { | |
| 		searchStr = searchStr[len(parts[0]):] | |
| 	} | |
| 	if len(parts[len(parts)-1]) > 0 { | |
| 		searchStr = searchStr[:len(searchStr)-len(parts[len(parts)-1])] | |
| 	} | |
| 
 | |
| 	for i := 1; i < len(parts)-1; i++ { | |
| 		if len(parts[i]) > 0 { | |
| 			index := strings.Index(searchStr, parts[i]) | |
| 			if index == -1 { | |
| 				return false | |
| 			} | |
| 			searchStr = searchStr[index+len(parts[i]):] | |
| 		} | |
| 	} | |
| 
 | |
| 	return true | |
| } | |
| 
 | |
| // matchWildcardRegex uses WildcardMatcher for patterns with ? wildcards | |
| func matchWildcardRegex(pattern, str string) bool { | |
| 	matcher, err := GetCachedWildcardMatcher(pattern) | |
| 	if err != nil { | |
| 		glog.Errorf("Error getting WildcardMatcher for pattern %s: %v. Falling back to matchWildcardString.", pattern, err) | |
| 		// Fallback to matchWildcardString | |
| 		return matchWildcardString(pattern, str) | |
| 	} | |
| 	return matcher.Match(str) | |
| } | |
| 
 | |
| // compileWildcardPattern converts a wildcard pattern to regex | |
| func compileWildcardPattern(pattern string) (*regexp.Regexp, error) { | |
| 	// Escape special regex characters except * and ? | |
| 	escaped := regexp.QuoteMeta(pattern) | |
| 
 | |
| 	// Replace escaped wildcards with regex equivalents | |
| 	escaped = strings.ReplaceAll(escaped, `\*`, `.*`) | |
| 	escaped = strings.ReplaceAll(escaped, `\?`, `.`) | |
| 
 | |
| 	// Anchor the pattern | |
| 	escaped = "^" + escaped + "$" | |
| 
 | |
| 	return regexp.Compile(escaped) | |
| }
 |