blob: 284d2a86fadcf0452707a0c7d81a36ecf4934fe3 [file] [log] [blame]
// Copyright 2016 The Upspin Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package server implements DirServer using a Tree as backing.
package server
import (
"io/ioutil"
"strings"
"sync"
"time"
"upspin.io/access"
"upspin.io/cache"
"upspin.io/cloud/storage"
"upspin.io/dir/server/serverlog"
"upspin.io/dir/server/tree"
"upspin.io/errors"
"upspin.io/log"
"upspin.io/metric"
"upspin.io/pack"
"upspin.io/path"
"upspin.io/serverutil"
"upspin.io/shutdown"
"upspin.io/upspin"
"upspin.io/user"
"upspin.io/valid"
)
// common error values.
var (
errNotExist = errors.E(errors.NotExist)
errPrivate = errors.E(errors.Private)
)
const (
// entryMustBeClean is used with lookup to specify whether the caller
// needs to look at the dir entry's references and therefore whether the
// tree must be flushed if a dirty dir entry is found.
entryMustBeClean = true
)
// server implements upspin.DirServer.
type server struct {
// serverConfig holds this server's Factotum, server name and store
// endpoint where to store dir entries. It is set when the server is
// first registered and never reset again.
serverConfig upspin.Config
// userName is the name of the user on behalf of whom this
// server is serving.
userName upspin.UserName
// baseUser, suffix and domain are the components of userName as parsed
// by user.Parse.
userBase, userSuffix, userDomain string
// logDir is the directory path accessible through the local file system
// where user logs are stored.
logDir string
// userTrees keeps track of user trees in LRU fashion, where key
// is an upspin.UserName and value is the tree.Tree for that user name.
// Access to userTrees must be protected by the user lock. Get the
// user lock by calling userLock(userName) and take it prior to getting
// a Tree from the userTree and while using the Tree. Concurrent access
// for different users is okay as the LRU is thread-safe.
userTrees *cache.LRU
// access caches the parsed contents of Access files as struct
// accessEntry, indexed by their path names.
access *cache.LRU
// defaultAccess caches parsed empty Access files that implicitly exists
// at the root of every user's tree, if an explicit one is not found.
// It's indexed by the username.
defaultAccess *cache.LRU
// remoteGroups caches groupEntry objects that store remote Group files
// that must be periodically forgotten so they're reloaded fresh again
// when needed.
remoteGroups *cache.LRU
// userLocks is a pool of user locks. To find the correct lock for a
// user, a string hash of a username selects the index into the slice to
// use. This fixed pool ensures we don't have a growing number of locks
// and that we also don't have a race creating new locks when we first
// touch a user.
userLocks []sync.Mutex
// snapshotControl is a channel for passing control messages to the
// snapshot loop.
snapshotControl chan snapshotCreate
// now returns the time now. It's usually just upspin.Now but is
// overridden for tests.
now func() upspin.Time
// dialed reports whether the instance was created using Dial, not New.
dialed bool
// The Storage backend in which to make backup copies of roots.
// If nil, no backups are made.
storage storage.Storage
}
// snapshotCreate is used to create a snapshot and report its success.
type snapshotCreate struct {
userName upspin.UserName
created chan error
}
var _ upspin.DirServer = (*server)(nil)
// options are optional parameters to almost every inner method of directory
// for doing optional, non-correctness-related work.
type options struct {
span *metric.Span
// Add other things below (for example, some health monitoring stats).
}
// New creates a new instance of DirServer with the given options
func New(cfg upspin.Config, options ...string) (upspin.DirServer, error) {
const op errors.Op = "dir/server.New"
if cfg == nil {
return nil, errors.E(op, errors.Invalid, "nil config")
}
if cfg.DirEndpoint().Transport == upspin.Unassigned {
return nil, errors.E(op, errors.Invalid, "directory endpoint cannot be unassigned")
}
if cfg.KeyEndpoint().Transport == upspin.Unassigned {
return nil, errors.E(op, errors.Invalid, "key endpoint cannot be unassigned")
}
if cfg.StoreEndpoint().Transport == upspin.Unassigned {
return nil, errors.E(op, errors.Invalid, "store endpoint cannot be unassigned")
}
if cfg.UserName() == "" {
return nil, errors.E(op, errors.Invalid, "empty user name")
}
if cfg.Factotum() == nil {
return nil, errors.E(op, errors.Invalid, "nil factotum")
}
// Check which options are present and pick suitable defaults.
var (
logDir string
storageBackend string
storageOpts []storage.DialOpts
)
for _, opt := range options {
const logDirPrefix = "logDir="
if strings.HasPrefix(opt, logDirPrefix) {
logDir = opt[len(logDirPrefix):]
continue
}
const backendPrefix = "backend="
if strings.HasPrefix(opt, backendPrefix) {
storageBackend = opt[len(backendPrefix):]
continue
}
storageOpts = append(storageOpts, storage.WithOptions(opt))
}
if logDir == "" {
dir, err := ioutil.TempDir("", "DirServer")
if err != nil {
return nil, errors.E(op, errors.IO, err)
}
log.Error.Printf("%s: warning: writing important logs to a temporary directory (%q). A server restart will lose data.", op, dir)
logDir = dir
}
var store storage.Storage
if storageBackend != "" {
// Dial a storage backend in which to store the roots.
var err error
store, err = storage.Dial(storageBackend, storageOpts...)
if err != nil {
return nil, errors.E(op, err)
}
}
const (
userCacheSize = 1000
accessCacheSize = 1000
groupCacheSize = 100
)
s := &server{
serverConfig: cfg,
userName: cfg.UserName(),
logDir: logDir,
userTrees: cache.NewLRU(userCacheSize),
access: cache.NewLRU(accessCacheSize),
defaultAccess: cache.NewLRU(accessCacheSize),
remoteGroups: cache.NewLRU(groupCacheSize),
userLocks: make([]sync.Mutex, numUserLocks),
now: upspin.Now,
storage: store,
}
shutdown.Handle(s.shutdown)
// Start background services.
s.startSnapshotLoop()
go s.groupRefreshLoop()
return s, nil
}
// Lookup implements upspin.DirServer.
func (s *server) Lookup(name upspin.PathName) (*upspin.DirEntry, error) {
const op errors.Op = "dir/server.Lookup"
o, m := newOptMetric(op)
defer m.Done()
return s.lookupWithPermissions(op, name, o)
}
func (s *server) lookupWithPermissions(op errors.Op, name upspin.PathName, opts ...options) (*upspin.DirEntry, error) {
p, err := path.Parse(name)
if err != nil {
return nil, errors.E(op, name, err)
}
entry, err := s.lookup(p, entryMustBeClean, opts...)
// Check if the user can know about the file at all. If not, to prevent
// leaking its existence, return Private.
if err == upspin.ErrFollowLink {
return s.errLink(op, entry, opts...)
}
if err != nil {
if errors.Is(errors.NotExist, err) {
if canAny, _, err := s.hasRight(access.AnyRight, p, opts...); err != nil {
return nil, errors.E(op, err)
} else if !canAny {
return nil, errors.E(op, name, errors.Private)
}
}
return nil, errors.E(op, err)
}
// Check for Read access permission.
canRead, _, err := s.hasRight(access.Read, p, opts...)
if err == upspin.ErrFollowLink {
return nil, errors.E(op, errors.Internal, p.Path(), "can't be link at this point")
}
if err != nil {
return nil, errors.E(op, err)
}
if !canRead {
canAny, _, err := s.hasRight(access.AnyRight, p, opts...)
if err != nil {
return nil, errors.E(op, err)
}
if !canAny {
return nil, s.errPerm(op, p, opts...)
}
if !access.IsAccessControlFile(entry.SignedName) {
entry.MarkIncomplete()
}
}
return entry, nil
}
// lookup implements Lookup for a parsed path. It is used by Lookup as well as
// by put. If entryMustBeClean is true, the returned entry is guaranteed to have
// valid references in its DirBlocks.
func (s *server) lookup(p path.Parsed, entryMustBeClean bool, opts ...options) (*upspin.DirEntry, error) {
o, ss := subspan("lookup", opts)
defer ss.End()
tree, err := s.loadTreeFor(p.User(), o)
if err != nil {
return nil, err
}
entry, dirty, err := tree.Lookup(p)
if err != nil {
// This could be ErrFollowLink so return the entry as well.
return entry, err
}
if dirty && entryMustBeClean {
// Flush and repeat.
err = tree.Flush()
if err != nil {
return nil, err
}
entry, dirty, err = tree.Lookup(p)
if err != nil {
return nil, err
}
if dirty {
return nil, errors.E(errors.Internal, "flush didn't clean entry")
}
}
if entry.IsLink() {
return entry, upspin.ErrFollowLink
}
return entry, nil
}
// Put implements upspin.DirServer.
func (s *server) Put(entry *upspin.DirEntry) (*upspin.DirEntry, error) {
const op errors.Op = "dir/server.Put"
o, m := newOptMetric(op)
defer m.Done()
err := valid.DirEntry(entry)
if err != nil {
return nil, errors.E(op, err)
}
p, err := path.Parse(entry.Name)
if err != nil {
return nil, errors.E(op, entry.Name, err)
}
// Special check for the magic file that trigger a snapshot operation.
// Only the snapshot owner can do it.
if isSnapshotUser(p.User()) && s.isSnapshotOwner(p.User()) && isSnapshotControlFile(p) {
err = isValidSnapshotControlEntry(entry)
if err != nil {
return nil, errors.E(op, err)
}
// Start a snapshot for this user.
errorC := make(chan error)
s.snapshotControl <- snapshotCreate{
userName: p.User(),
created: errorC,
}
return entry, <-errorC // Returned error reports status of snapshot.
}
isAccess := access.IsAccessFile(p.Path())
isGroup := access.IsGroupFile(p.Path())
isLink := entry.IsLink()
// Links can't be named Access or Group.
if isLink {
if isAccess || isGroup {
return nil, errors.E(op, p.Path(), errors.Invalid, "link cannot be named Access or Group")
}
}
// Directories cannot have reserved names.
if isAccess && entry.IsDir() {
return nil, errors.E(op, errors.Invalid, entry.Name, "cannot make directory named Access")
}
// Special files must use integrity pack (plain text + signature).
isGroupFile := isGroup && !entry.IsDir()
if isGroupFile || isAccess {
packer := pack.Lookup(entry.Packing)
if packer == nil {
return nil, errors.E(op, errors.Errorf("unknown packing %s", entry.Packing))
}
ok, err := packer.UnpackableByAll(entry)
if err != nil {
return nil, errors.E(op, err)
}
if !ok {
return nil, errors.E(op, p.Path(), "Access or Group files must be readable by all")
}
}
if isAccess {
// Validate access files at Put time to detect bad ones early.
_, err := s.loadAccess(entry, o)
if err != nil {
return nil, errors.E(op, err)
}
}
if isGroupFile {
// Validate group files at Put time to detect bad ones early.
err = s.loadGroup(p, entry)
if err != nil {
return nil, errors.E(op, err)
}
// Check that the name is a legal Group name.
// All elements must satisfy this condition, to protect Access file parsing.
// TODO: Is this the syntax we should require for any Upspin name?
for i := 1; i < p.NElem(); i++ { // Element 0 is "Group".
if _, _, err := user.ParseUser(p.Elem(i)); err != nil {
return nil, errors.E(op, entry.Name, err)
}
}
}
// Check for links along the path.
existingEntry, err := s.lookup(p, !entryMustBeClean, o)
if err == upspin.ErrFollowLink {
return s.errLink(op, existingEntry, o)
}
if errors.Is(errors.NotExist, err) {
// OK; entry not found as expected. Can we create it?
canCreate, _, err := s.hasRight(access.Create, p, o)
if err == upspin.ErrFollowLink {
return nil, errors.E(op, p.Path(), errors.Internal, "unexpected ErrFollowLink")
}
if err != nil {
return nil, errors.E(op, err)
}
if !canCreate {
return nil, s.errPerm(op, p, o)
}
// The provided sequence number for a new item may be only SeqNotExist or SeqIgnore.
if entry.Sequence != upspin.SeqNotExist && entry.Sequence != upspin.SeqIgnore {
return nil, errors.E(op, p.Path(), errors.Invalid, "invalid sequence number")
}
} else if err != nil {
// Some unexpected error happened looking up path. Abort.
return nil, errors.E(op, err)
} else {
// Error is nil therefore path exists.
// Check if it's root.
if p.IsRoot() {
return nil, errors.E(op, p.Path(), errors.Exist)
}
// Check if we can overwrite.
if existingEntry.IsDir() {
return nil, errors.E(op, p.Path(), errors.Exist, "can't overwrite directory")
}
if entry.IsDir() {
return nil, errors.E(op, p.Path(), errors.Exist, "can't overwrite file with directory")
}
// To overwrite a file, we need Write permission.
canWrite, _, err := s.hasRight(access.Write, p, o)
if err == upspin.ErrFollowLink {
return nil, errors.E(op, p.Path(), errors.Internal, "unexpected ErrFollowLink")
}
if err != nil {
return nil, errors.E(op, err)
}
if !canWrite {
return nil, s.errPerm(op, p, o)
}
// If the file is expected not to be there, it's an error.
if entry.Sequence == upspin.SeqNotExist {
return nil, errors.E(op, entry.Name, errors.Exist)
}
// We also must have the correct sequence number or SeqIgnore.
if entry.Sequence != upspin.SeqIgnore {
if entry.Sequence != existingEntry.Sequence {
return nil, errors.E(op, entry.Name, errors.Invalid, "sequence number")
}
}
// If we're updating an Access file delete it from the cache and
// let it be re-loaded lazily when needed again.
if access.IsAccessFile(entry.Name) {
s.access.Remove(entry.Name)
}
// If we're updating a Group file, remove the old one from the
// access group cache. Let the new one be loaded lazily.
if access.IsGroupFile(entry.Name) {
err = access.RemoveGroup(entry.Name)
if err != nil {
// Nothing to do but log.
log.Error.Printf("%s: Error removing group file %s: %s", op, entry.Name, err)
}
}
}
entry, err = s.put(op, p, entry, o)
if err != nil {
return entry, err
}
// Return Incomplete entry with Sequence number.
retEntry := &upspin.DirEntry{
Attr: upspin.AttrIncomplete,
Sequence: entry.Sequence,
}
return retEntry, nil
}
// put performs Put on the user's tree.
func (s *server) put(op errors.Op, p path.Parsed, entry *upspin.DirEntry, opts ...options) (*upspin.DirEntry, error) {
o, ss := subspan("put", opts)
defer ss.End()
tree, err := s.loadTreeFor(p.User(), o)
if err != nil {
return nil, errors.E(op, err)
}
entry, err = tree.Put(p, entry)
if err == upspin.ErrFollowLink {
return entry, err
}
if err != nil {
return nil, errors.E(op, p.Path(), err)
}
return entry, nil
}
// Glob implements upspin.DirServer.
func (s *server) Glob(pattern string) ([]*upspin.DirEntry, error) {
const op errors.Op = "dir/server.Glob"
o, m := newOptMetric(op)
defer m.Done()
// lookup implements serverutil.LookupFunc. It checks permissions.
lookup := func(name upspin.PathName) (*upspin.DirEntry, error) {
const op errors.Op = "dir/server.Lookup"
o, ss := subspan(op, []options{o})
defer ss.End()
return s.lookupWithPermissions(op, name, o)
}
// lookup implements serverutil.ListFunc. It checks permissions.
listDir := func(dirName upspin.PathName) ([]*upspin.DirEntry, error) {
const op errors.Op = "dir/server.listDir"
o, ss := subspan(op, []options{o})
defer ss.End()
return s.listDir(op, dirName, o)
}
entries, err := serverutil.Glob(pattern, lookup, listDir)
if err != nil && err != upspin.ErrFollowLink {
err = errors.E(op, err)
}
return entries, err
}
// listDir implements serverutil.ListFunc, with an additional options variadic.
// dirName should always be a directory. It checks permissions.
func (s *server) listDir(op errors.Op, dirName upspin.PathName, opts ...options) ([]*upspin.DirEntry, error) {
parsed, err := path.Parse(dirName)
if err != nil {
return nil, errors.E(op, err)
}
tree, err := s.loadTreeFor(parsed.User(), opts...)
if err != nil {
return nil, errors.E(op, err)
}
// Fetch the directory's contents. Don't return the error from List
// until we know if we have List rights.
entries, isDirty, listErr := tree.List(parsed)
if listErr == upspin.ErrFollowLink {
entry, err := s.errLink(op, entries[0], opts...)
if entry != nil {
return []*upspin.DirEntry{entry}, err
}
return nil, err
}
canList, canRead := false, false
// Check that we have list rights for any file in the directory.
canList, _, err = s.hasRight(access.List, parsed, opts...)
if err != nil {
// TODO(adg): this error needs sanitizing
return nil, errors.E(op, dirName, err)
}
if !canList {
return nil, errors.E(op, dirName, errors.Private)
}
if listErr != nil {
return nil, errors.E(op, listErr)
}
canRead, _, _ = s.hasRight(access.Read, parsed, opts...)
if canRead && isDirty {
// User wants DirEntries with valid blocks, so we must flush
// the Tree if something is dirty and try again.
err = tree.Flush()
if err != nil {
return nil, errors.E(op, err)
}
entries, _, err = tree.List(parsed)
if err != nil { // Not ErrFollowLink
return nil, errors.E(op, err)
}
}
if !canRead {
for _, e := range entries {
if !access.IsAccessControlFile(e.SignedName) {
e.MarkIncomplete()
}
}
}
return entries, nil
}
// Delete implements upspin.DirServer.
func (s *server) Delete(name upspin.PathName) (*upspin.DirEntry, error) {
const op errors.Op = "dir/server.Delete"
o, m := newOptMetric(op)
defer m.Done()
p, err := path.Parse(name)
if err != nil {
return nil, errors.E(op, name, err)
}
canDelete, link, err := s.hasRight(access.Delete, p, o)
if err == upspin.ErrFollowLink {
return s.errLink(op, link, o)
}
if err != nil {
return nil, errors.E(op, err)
}
if !canDelete {
return nil, s.errPerm(op, p, o)
}
// Load the tree for this user.
t, err := s.loadTreeFor(p.User(), o)
if err != nil {
return nil, errors.E(op, err)
}
entry, err := t.Delete(p)
if err != nil {
return entry, err // could be ErrFollowLink.
}
// If we just deleted an Access file, remove it from the access cache
// too.
if access.IsAccessFile(p.Path()) {
s.access.Remove(p.Path())
}
// If we just deleted a Group file, remove it from the Group cache too.
if access.IsGroupFile(p.Path()) {
err = access.RemoveGroup(p.Path())
if err != nil {
// Nothing to do but log (it may not have been loaded
// yet, so it's not an error).
log.Printf("%s: Error removing group file: %s", op, err)
}
}
// If we just deleted the root, close the tree, remove it from the cache
// and delete all logs associated with the tree owner.
if p.IsRoot() {
user := t.User()
if err := s.closeTree(p.User()); err != nil {
return nil, errors.E(op, name, err)
}
if err := user.DeleteLogs(); err != nil {
return nil, errors.E(op, name, err)
}
}
return entry, nil
}
// WhichAccess implements upspin.DirServer.
func (s *server) WhichAccess(name upspin.PathName) (*upspin.DirEntry, error) {
const op errors.Op = "dir/server.WhichAccess"
o, m := newOptMetric(op)
defer m.Done()
p, err := path.Parse(name)
if err != nil {
return nil, errors.E(op, name, err)
}
// Check whether the user has Any right on p.
hasAny, link, err := s.hasRight(access.AnyRight, p, o)
if err == upspin.ErrFollowLink {
return s.errLink(op, link, o)
}
if err != nil {
return nil, errors.E(op, err)
}
if !hasAny {
return nil, errors.E(op, errors.Private, name)
}
return s.whichAccess(p, o)
}
// Watch implements upspin.DirServer.Watch.
func (s *server) Watch(name upspin.PathName, sequence int64, done <-chan struct{}) (<-chan upspin.Event, error) {
const op errors.Op = "dir/server.Watch"
o, m := newOptMetric(op)
defer m.Done()
p, err := path.Parse(name)
if err != nil {
return nil, errors.E(op, name, err)
}
// Don't permit Watches of snapshot trees.
// See issue #536.
if isSnapshotUser(p.User()) {
return nil, upspin.ErrNotSupported
}
tree, err := s.loadTreeFor(p.User(), o)
if err != nil {
return nil, errors.E(op, err)
}
// Establish a channel with the tree and start a goroutine that filters
// out requests not visible by the caller.
treeEvents, err := tree.Watch(p, sequence, done)
if err != nil {
return nil, errors.E(op, err)
}
events := make(chan upspin.Event, 1)
go s.watch(op, treeEvents, events)
return events, nil
}
// watcher runs in a goroutine reading events from the tree and passing them
// along to the original caller, but first verifying whether the user has rights
// to know about the event.
func (s *server) watch(op errors.Op, treeEvents <-chan *upspin.Event, outEvents chan<- upspin.Event) {
const sendTimeout = time.Minute
t := time.NewTimer(sendTimeout)
defer close(outEvents)
defer t.Stop()
sendEvent := func(e *upspin.Event) bool {
// Send e on outEvents, with a timeout.
if !t.Stop() {
<-t.C
}
t.Reset(sendTimeout)
select {
case outEvents <- *e:
// OK, sent.
return true
case <-t.C:
// Timed out.
log.Printf("%s: timeout sending event for %s", op, s.userName)
return false
}
}
for {
e, ok := <-treeEvents
if !ok {
// Tree closed channel. Close outgoing event as well.
return
}
if e.Entry == nil {
// It's likely an error. Pass it along. We're sure to
// have treeEvents closed in the next loop.
sendEvent(e)
continue
}
// Check permissions on e.Entry.
p, err := path.Parse(e.Entry.Name)
if err != nil {
sendEvent(&upspin.Event{Error: errors.E(op, err)})
return
}
hasAny, _, err := s.hasRight(access.AnyRight, p)
if err != nil {
sendEvent(&upspin.Event{Error: errors.E(op, err)})
return
}
if !hasAny {
continue
}
hasRead, _, err := s.hasRight(access.Read, p)
if err != nil {
sendEvent(&upspin.Event{Error: errors.E(op, err)})
return
}
if !hasRead {
if !access.IsAccessControlFile(e.Entry.SignedName) {
e.Entry.MarkIncomplete()
}
}
if !sendEvent(e) {
return
}
}
}
// Dial implements upspin.Dialer.
func (s *server) Dial(ctx upspin.Config, e upspin.Endpoint) (upspin.Service, error) {
const op errors.Op = "dir/server.Dial"
if e.Transport == upspin.Unassigned {
return nil, errors.E(op, errors.Invalid, "transport must not be unassigned")
}
if err := valid.UserName(ctx.UserName()); err != nil {
return nil, errors.E(op, errors.Invalid, err)
}
cp := *s // copy of the generator instance.
// Overwrite the userName and its sub-components (base, suffix, domain).
cp.userName = ctx.UserName()
cp.dialed = true
var err error
cp.userBase, cp.userSuffix, cp.userDomain, err = user.Parse(cp.userName)
if err != nil {
return nil, errors.E(op, err)
}
// create a default Access file for this user and cache it.
defaultAccess, err := access.New(upspin.PathName(cp.userName + "/"))
if err != nil {
return nil, errors.E(op, err)
}
cp.defaultAccess.Add(cp.userName, defaultAccess)
return &cp, nil
}
// Endpoint implements upspin.Service.
func (s *server) Endpoint() upspin.Endpoint {
return s.serverConfig.DirEndpoint()
}
// Close implements upspin.Service.
func (s *server) Close() {
const op errors.Op = "dir/server.Close"
// Remove this user's tree from the cache. This allows it to be
// garbage-collected even if other servers have pointers into the
// cache (which at least one will have, the one created with New).
if err := s.closeTree(s.userName); err != nil {
log.Error.Printf("%s: Error closing user tree %q: %q", op, s.userName, err)
}
if !s.dialed {
s.shutdown()
}
}
func (s *server) closeTree(user upspin.UserName) error {
defer s.userLock(s.userName).Unlock()
if t, ok := s.userTrees.Remove(user).(*tree.Tree); ok {
// Close will flush and release all resources.
if err := t.Close(); err != nil {
return err
}
}
return nil
}
// loadTreeFor loads the user's tree, if it exists.
func (s *server) loadTreeFor(userName upspin.UserName, opts ...options) (*tree.Tree, error) {
defer span(opts).StartSpan("loadTreeFor").End()
if err := valid.UserName(userName); err != nil {
return nil, errors.E(errors.Invalid, err)
}
defer s.userLock(s.userName).Unlock()
// Do we have a cached tree for this user already?
if val, found := s.userTrees.Get(userName); found {
if tree, ok := val.(*tree.Tree); ok {
return tree, nil
}
// This should never happen because we only store type tree.Tree in the userTree.
return nil, errors.E(userName, errors.Internal,
errors.Errorf("userTrees contained value of unexpected type %T", val))
}
// User is not in the cache. Load a tree from the logs, if they exist.
hasLog, err := serverlog.HasLog(userName, s.logDir)
if err != nil {
return nil, err
}
if !hasLog && !s.canCreateRoot(userName) {
// Tree for user does not exist and the logged-in user is not
// allowed to create it.
return nil, errNotExist
}
user, err := serverlog.Open(userName, s.logDir, s.serverConfig.Factotum(), s.storage)
if err != nil {
return nil, err
}
// If user has root, we can load the tree from it.
if _, err := user.Root(); err != nil {
// Likely the user has no root yet.
if !errors.Is(errors.NotExist, err) {
// No it's some other error. Abort.
return nil, err
}
// Ok, let it proceed. The user will still need to make the
// root, but we allow setting up a new tree for now.
err = user.SaveOffset(0)
if err != nil {
return nil, err
}
// Fall through and load a new tree.
}
// Create a new tree for the user.
tree, err := tree.New(s.serverConfig, user)
if err != nil {
return nil, err
}
// Add to the cache and return
s.userTrees.Add(userName, tree)
return tree, nil
}
// canCreateRoot reports whether the current user can create a root for the
// named user.
func (s *server) canCreateRoot(user upspin.UserName) bool {
if s.userName == user {
return true
}
if isSnapshotUser(user) && s.isSnapshotOwner(user) {
return true
}
return false
}
// errPerm checks whether the user has any right to the given path, and if so
// returns a Permission error. Otherwise it returns a Private error.
// This is used to prevent probing of the name space.
func (s *server) errPerm(op errors.Op, p path.Parsed, opts ...options) error {
// Before returning, check that the user has the right to know,
// to prevent leaking the name space.
if hasAny, _, err := s.hasRight(access.AnyRight, p, opts...); err != nil {
// Some error other than ErrFollowLink.
return errors.E(op, err)
} else if !hasAny {
// User does not have Any right. Return a 'Private' error.
return errors.E(op, p.Path(), errors.Private)
}
return errors.E(op, p.Path(), access.ErrPermissionDenied)
}
// errLink checks whether the user has any right to the given entry, and if so
// returns the entry and ErrFollowLink. If the use has no rights, it returns a
// NotExist error. This is used to prevent probing of the name space using
// links.
func (s *server) errLink(op errors.Op, link *upspin.DirEntry, opts ...options) (*upspin.DirEntry, error) {
p, err := path.Parse(link.Name)
if err != nil {
return nil, errors.E(op, errors.Internal, link.Name, err)
}
if hasAny, _, err := s.hasRight(access.AnyRight, p, opts...); err != nil {
// Some error other than ErrFollowLink.
return nil, errors.E(op, err)
} else if hasAny {
// User has Any right on the link. Let them follow it.
return link, upspin.ErrFollowLink
}
// Denied. User has no right on link. Return a 'Private' error.
return nil, errors.E(op, p.Path(), errors.Private)
}
// shutdown is called when the server is being forcefully shut down.
func (s *server) shutdown() {
it := s.userTrees.NewIterator()
for {
k, _, next := it.GetAndAdvance()
if !next {
break
}
user := k.(upspin.UserName)
err := s.closeTree(user)
if err != nil {
log.Printf("error closing tree for user %s: %v", user, err)
}
}
}
// newOptMetric creates a new options populated with a metric for operation op.
func newOptMetric(op errors.Op) (options, *metric.Metric) {
m, sp := metric.NewSpan(op)
opts := options{
span: sp,
}
return opts, m
}
// span returns the first span found in opts or a new one if not found.
func span(opts []options) *metric.Span {
for _, o := range opts {
if o.span != nil {
return o.span
}
}
// This is probably an error. Metrics should be created at the entry
// points only.
return metric.New("FIXME").StartSpan("FIXME")
}
// subspan creates a span for an operation op in the given option. It returns
// a new option with the new span, for passing along subfunctions.
func subspan(op errors.Op, opts []options) (options, *metric.Span) {
s := span(opts).StartSpan(op)
return options{span: s}, s
}