blob: e6b84b763be453a9be05ecef2d40dfd9aed0665a [file] [log] [blame]
package state
import (
"reflect"
"veyron/services/store/memstore/acl"
"veyron/services/store/memstore/field"
"veyron/services/store/memstore/refs"
"veyron2/security"
"veyron2/storage"
)
type Snapshot interface {
// NewIterator returns an Iterator that starts with the value at <path>. If
// filter is given it is used to limit traversal beneath certain paths.
// filter can be specified to limit the results of the iteration. If filter
// is nil, all decendents of the specified path are returned.
NewIterator(pid security.PublicID, path storage.PathName, filter IterFilter) Iterator
// PathMatch returns true iff there is a name for the store value that
// matches the pathRegex.
PathMatch(pid security.PublicID, id storage.ID, regex *PathRegex) bool
// Find performs a lookup based on storage.ID, returning nil if the cell is not found.
Find(id storage.ID) *Cell
// Get returns the value for a path.
Get(pid security.PublicID, path storage.PathName) (*storage.Entry, error)
}
// Snapshot keeps the state for the store. The snapshot contains a dictionary
// and a root,
//
// idTable : storage.ID -> storage.Value
// rootID : storage.ID
//
// Snapshots support isolation by using a functional/immutable dictionary for the
// idTable.
//
// Paths are resolved by traversing the snapshot from the root, using reflection to
// traverse fields within each of the values. For example, to resolve a path
// /a/b/c/d/e/f/g/h, we perform the following steps.
//
// id1 := idTable[rootID].a.b.c
// id2 := idTable[id1].d.e
// id3 := idTable[id2].f.g.h
// return id3
//
// If any of those resolution steps fails (if the idTable doesn't contain an
// entry, or a field path like .a.b.c doesn't exist), then the resolution fails.
type snapshot struct {
// idTable is the dictionary of values. We use functional sets to make it
// easy to perform snapshotting.
idTable cellSet
// rootID is the identifier of the root object.
rootID storage.ID
// aclCache caches a set of ACLs.
aclCache acl.Cache
// defaultACLSet is the ACLSet used to access the root directory.
defaultACLSet acl.Set
}
// newSnapshot returns an empty snapshot.
func newSnapshot(admin security.PublicID) snapshot {
sn := snapshot{
idTable: emptyIDTable,
defaultACLSet: makeDefaultACLSet(admin),
}
sn.aclCache = acl.NewCache(sn.makeFindACLFunc())
return sn
}
// resetACLCache resets the aclCache.
func (sn *snapshot) resetACLCache() {
sn.aclCache.UpdateFinder(sn.makeFindACLFunc())
}
// Find performs a lookup based on storage.ID, returning nil if the cell is not found.
func (sn *snapshot) Find(id storage.ID) *Cell {
v, ok := sn.idTable.Get(&Cell{ID: id})
if !ok {
return nil
}
return v.(*Cell)
}
// Get implements the Snapshot method.
func (sn *snapshot) Get(pid security.PublicID, path storage.PathName) (*storage.Entry, error) {
checker := sn.newPermChecker(pid)
// Pass nil for 'mutations' since the snapshot is immutable.
cell, suffix, v := sn.resolveCell(checker, path, nil)
if cell == nil {
return nil, ErrNotFound
}
var e *storage.Entry
if len(suffix) == 0 {
e = cell.getEntry()
} else {
e = newSubfieldEntry(v)
}
return e, nil
}
// resolveCell performs a path-based lookup, traversing the state from the
// root.
//
// Returns (cell, suffix, v), where cell contains the value, suffix is the path
// to the value, v is the value itself. If the operation failed, the returned
// cell is nil.
func (sn *snapshot) resolveCell(checker *acl.Checker, path storage.PathName, mu *Mutations) (*Cell, storage.PathName, interface{}) {
// Get the starting object.
id, suffix, ok := path.GetID()
if ok {
path = suffix
checker.Update(uidTagList)
} else {
id = sn.rootID
}
return sn.resolve(checker, id, path, mu)
}
func (sn *snapshot) resolve(checker *acl.Checker, id storage.ID, path storage.PathName, mu *Mutations) (*Cell, storage.PathName, interface{}) {
cell := sn.Find(id)
if cell == nil {
return nil, nil, nil
}
for {
if mu != nil {
mu.addPrecondition(cell)
}
checker.Update(cell.Tags)
var v reflect.Value
var suffix storage.PathName
if len(path) > 0 && path[0] == refs.TagsDirName {
if !checker.IsAllowed(security.AdminLabel) {
// Access to .tags requires admin priviledges.
return nil, nil, ErrPermissionDenied
}
v, suffix = field.Get(cell.Tags, path[1:])
} else {
if !checker.IsAllowed(security.ReadLabel) {
// Do not return errPermissionDenied because that would leak the
// existence of the inaccessible value.
return nil, nil, nil
}
v, suffix = field.Get(cell.Value, path)
}
x := v.Interface()
if id, ok := x.(storage.ID); ok {
// Always dereference IDs.
cell = sn.Find(id)
path = suffix
continue
}
switch len(suffix) {
case 0:
// The path is fully resolved. We're done.
return cell, path, x
case len(path):
// The path couldn't be resolved at all. It must be an entry in the
// implicit directory.
r, ok := cell.Dir.Get(&refs.Ref{Path: refs.NewSingletonPath(path[0])})
if !ok {
return nil, nil, nil
}
cell = sn.Find(r.(*refs.Ref).ID)
path = path[1:]
default:
// The path is partially resolved, but it does not resolve to a
// storage.ID. This is an error.
return nil, nil, nil
}
}
}