// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 package pathmanager import ( "strings" "sync" iradix "github.com/hashicorp/go-immutable-radix" ) // PathManager is a prefix searchable index of paths type PathManager struct { l sync.RWMutex paths *iradix.Tree } // New creates a new path manager func New() *PathManager { return &PathManager{ paths: iradix.New(), } } // AddPaths adds path to the paths list func (p *PathManager) AddPaths(paths []string) { p.l.Lock() defer p.l.Unlock() txn := p.paths.Txn() for _, prefix := range paths { if len(prefix) == 0 { continue } var exception bool if strings.HasPrefix(prefix, "!") { prefix = strings.TrimPrefix(prefix, "!") exception = true } // We trim any trailing *, but we don't touch whether it is a trailing // slash or not since we want to be able to ignore prefixes that fully // specify a file txn.Insert([]byte(strings.TrimSuffix(prefix, "*")), exception) } p.paths = txn.Commit() } // RemovePaths removes paths from the paths list func (p *PathManager) RemovePaths(paths []string) { p.l.Lock() defer p.l.Unlock() txn := p.paths.Txn() for _, prefix := range paths { if len(prefix) == 0 { continue } // Exceptions aren't stored with the leading ! so strip it if strings.HasPrefix(prefix, "!") { prefix = strings.TrimPrefix(prefix, "!") } // We trim any trailing *, but we don't touch whether it is a trailing // slash or not since we want to be able to ignore prefixes that fully // specify a file txn.Delete([]byte(strings.TrimSuffix(prefix, "*"))) } p.paths = txn.Commit() } // RemovePathPrefix removes all paths with the given prefix func (p *PathManager) RemovePathPrefix(prefix string) { p.l.Lock() defer p.l.Unlock() // We trim any trailing *, but we don't touch whether it is a trailing // slash or not since we want to be able to ignore prefixes that fully // specify a file p.paths, _ = p.paths.DeletePrefix([]byte(strings.TrimSuffix(prefix, "*"))) } // Len returns the number of paths func (p *PathManager) Len() int { return p.paths.Len() } // Paths returns the path list func (p *PathManager) Paths() []string { p.l.RLock() defer p.l.RUnlock() paths := make([]string, 0, p.paths.Len()) walkFn := func(k []byte, v interface{}) bool { paths = append(paths, string(k)) return false } p.paths.Root().Walk(walkFn) return paths } // HasPath returns if the prefix for the path exists regardless if it is a path // (ending with /) or a prefix for a leaf node func (p *PathManager) HasPath(path string) bool { p.l.RLock() defer p.l.RUnlock() if _, exceptionRaw, ok := p.paths.Root().LongestPrefix([]byte(path)); ok { var exception bool if exceptionRaw != nil { exception = exceptionRaw.(bool) } return !exception } return false } // HasExactPath returns if the longest match is an exact match for the // full path func (p *PathManager) HasExactPath(path string) bool { p.l.RLock() defer p.l.RUnlock() if val, exceptionRaw, ok := p.paths.Root().LongestPrefix([]byte(path)); ok { var exception bool if exceptionRaw != nil { exception = exceptionRaw.(bool) } strVal := string(val) if strings.HasSuffix(strVal, "/") || strVal == path { return !exception } } return false }