blob: f5db872c1b26b4d94d7534c4490feee51405f26a [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 client implements a simple client service talking to services
// running anywhere (GCP, InProcess, etc).
package client // import "upspin.io/client"
import (
"fmt"
"strings"
"upspin.io/access"
"upspin.io/bind"
"upspin.io/client/clientutil"
"upspin.io/client/file"
"upspin.io/errors"
"upspin.io/flags"
"upspin.io/metric"
"upspin.io/pack"
"upspin.io/path"
"upspin.io/upspin"
_ "upspin.io/pack/eeintegrity"
_ "upspin.io/pack/plain"
)
// Client implements upspin.Client.
type Client struct {
config upspin.Config
}
var _ upspin.Client = (*Client)(nil)
const (
followFinalLink = true
doNotFollowFinalLink = false
)
// New creates a Client that uses the given configuration to
// access the various Upspin servers.
func New(config upspin.Config) upspin.Client {
return &Client{config: config}
}
// PutLink implements upspin.Client.
func (c *Client) PutLink(oldName, linkName upspin.PathName) (*upspin.DirEntry, error) {
const op errors.Op = "client.PutLink"
m, s := newMetric(op)
defer m.Done()
if access.IsAccessControlFile(oldName) {
return nil, errors.E(op, oldName, errors.Invalid, "cannot link to Access or Group file")
}
if access.IsAccessControlFile(linkName) {
return nil, errors.E(op, linkName, errors.Invalid, "cannot create link named Access or Group")
}
parsed, err := path.Parse(oldName)
if err != nil {
return nil, errors.E(op, err)
}
oldName = parsed.Path() // Make sure it's clean.
parsedLink, err := path.Parse(linkName)
if err != nil {
return nil, errors.E(op, err)
}
linkName = parsedLink.Path() // Make sure it's clean.
entry := &upspin.DirEntry{
Name: linkName,
SignedName: linkName,
Packing: upspin.PlainPack, // Unused but be explicit.
Time: upspin.Now(),
Sequence: upspin.SeqIgnore,
Writer: c.config.UserName(),
Link: oldName,
Attr: upspin.AttrLink,
}
// Record directory entry.
entry, _, err = c.lookup(op, entry, putLookupFn, doNotFollowFinalLink, s)
return entry, err
}
// Used by PutLink etc. but not by Put itself.
func putLookupFn(dir upspin.DirServer, entry *upspin.DirEntry, s *metric.Span) (*upspin.DirEntry, error) {
defer s.StartSpan("dir.Put").End()
e, err := dir.Put(entry)
// Put and friends must all return an entry. dir.Put only returns an incomplete one,
// with the updated sequence number.
if err != nil {
return e, err
}
if e != nil { // TODO: Can be nil only when talking to old servers.
entry.Sequence = e.Sequence
}
return entry, nil
}
// Put implements upspin.Client.
func (c *Client) Put(name upspin.PathName, data []byte) (*upspin.DirEntry, error) {
const op errors.Op = "client.Put"
m, s := newMetric(op)
defer m.Done()
parsed, err := path.Parse(name)
if err != nil {
return nil, errors.E(op, err)
}
// Find the Access file that applies. This will also cause us to evaluate links in the path,
// and if we do, evalEntry will contain the true file name of the Put operation we will do.
accessEntry, evalEntry, err := c.lookup(op, &upspin.DirEntry{Name: parsed.Path()}, whichAccessLookupFn, followFinalLink, s)
if err != nil {
return nil, errors.E(op, err)
}
name = evalEntry.Name
readers, err := c.getReaders(op, name, accessEntry)
if err != nil {
return nil, errors.E(op, name, err)
}
// Encrypt data according to the preferred packer
packer := pack.Lookup(c.config.Packing())
if packer == nil {
return nil, errors.E(op, name, errors.Errorf("unrecognized Packing %d", c.config.Packing()))
}
// Ensure Access file is valid.
if access.IsAccessFile(name) {
_, err := access.Parse(name, data)
if err != nil {
return nil, errors.E(op, name, err)
}
}
// Ensure Group file is valid.
if access.IsGroupFile(name) {
_, err := access.ParseGroup(parsed, data)
if err != nil {
return nil, errors.E(op, name, err)
}
}
entry := &upspin.DirEntry{
Name: name,
SignedName: name,
Packing: packer.Packing(),
Time: upspin.Now(),
Sequence: upspin.SeqIgnore,
Writer: c.config.UserName(),
Link: "",
Attr: upspin.AttrNone,
}
ss := s.StartSpan("pack")
if err := c.pack(entry, data, packer, ss); err != nil {
return nil, errors.E(op, err)
}
ss.End()
ss = s.StartSpan("addReaders")
if err := c.addReaders(op, entry, packer, readers); err != nil {
return nil, err
}
ss.End()
// We have evaluated links so can use DirServer.Put directly.
dir, err := c.DirServer(name)
if err != nil {
return nil, errors.E(op, err)
}
defer s.StartSpan("dir.Put").End()
e, err := dir.Put(entry)
if err != nil {
return e, err
}
// dir.Put returns an incomplete entry, with the updated sequence number.
if e != nil { // TODO: Can be nil only when talking to old servers.
entry.Sequence = e.Sequence
}
return entry, nil
}
// validSigner checks that the file signer is either the owner
// or else has write permission.
// The directory server already checks that entry.Writer
// has Write access. Only under the Prudent flag do we
// recheck, protecting against a bad directory server.
func (c *Client) validSigner(entry *upspin.DirEntry) error {
if !flags.Prudent {
return nil
}
parsed, err := path.Parse(entry.SignedName)
if err != nil {
return err
}
if parsed.User() == entry.Writer {
return nil
}
path := parsed.Path()
// We have walked the path, so no links, so we can query the DirServer ourselves.
dir, err := c.DirServer(path)
if err != nil {
return err
}
acc, err := c.access(path, dir)
if err != nil {
return err
}
canWrite, err := acc.Can(entry.Writer, access.Write, entry.SignedName, c.Get)
if err != nil {
return err
}
if canWrite {
return nil
}
return errors.E(errors.Invalid, parsed.User(), "signer does not have write permission")
}
// access returns an Access struct for the applicable, parsed Access file.
// Links have been evaluated so we can ask the DirServer directly.
func (c *Client) access(path upspin.PathName, dir upspin.DirServer) (*access.Access, error) {
whichAccess, err := dir.WhichAccess(path)
if err != nil || whichAccess == nil {
return nil, err
}
err = validateWhichAccess(path, whichAccess)
if err != nil {
return nil, err
}
accessData, err := c.Get(whichAccess.Name)
if err != nil {
return nil, err
}
return access.Parse(whichAccess.Name, accessData)
}
func (c *Client) pack(entry *upspin.DirEntry, data []byte, packer upspin.Packer, s *metric.Span) error {
// Verify the blocks aren't too big. This can't happen unless someone's modified
// flags.BlockSize underfoot, but protect anyway.
if flags.BlockSize > upspin.MaxBlockSize {
return errors.Errorf("block size too big: %d > %d", flags.BlockSize, upspin.MaxBlockSize)
}
// Start the I/O.
store, err := bind.StoreServer(c.config, c.config.StoreEndpoint())
if err != nil {
return err
}
bp, err := packer.Pack(c.config, entry)
if err != nil {
return err
}
for len(data) > 0 {
n := len(data)
if n > flags.BlockSize {
n = flags.BlockSize
}
ss := s.StartSpan("bp.pack")
cipher, err := bp.Pack(data[:n])
ss.End()
if err != nil {
return err
}
data = data[n:]
ss = s.StartSpan("store.Put")
refdata, err := store.Put(cipher)
ss.End()
if err != nil {
return err
}
bp.SetLocation(
upspin.Location{
Endpoint: c.config.StoreEndpoint(),
Reference: refdata.Reference,
},
)
}
return bp.Close()
}
func whichAccessLookupFn(dir upspin.DirServer, entry *upspin.DirEntry, s *metric.Span) (*upspin.DirEntry, error) {
defer s.StartSpan("dir.WhichAccess").End()
whichEntry, err := dir.WhichAccess(entry.Name)
if err != nil {
return whichEntry, err
}
return whichEntry, validateWhichAccess(entry.Name, whichEntry)
}
// validateWhichAccess validates that the DirEntry for an Access file as
// returned by the DirServer's WhichAccess function for name is not forged.
func validateWhichAccess(name upspin.PathName, accessEntry *upspin.DirEntry) error {
if accessEntry == nil {
return nil
}
// The directory of the Access entry must be a prefix of the path name
// requested, and the signing key on the returned Access file must be
// root of the pathname.
namePath, err := path.Parse(name)
if err != nil {
return err
}
if !access.IsAccessFile(accessEntry.Name) {
return errors.E(errors.Internal, accessEntry.Name, "not an Access file")
}
accessPath, err := path.Parse(accessEntry.Name)
if err != nil {
return err
}
accessDir := accessPath.Drop(1) // Remove the "/Access" part.
if !namePath.HasPrefix(accessDir) {
return errors.E(errors.Invalid, accessPath.Path(), errors.Errorf("access file is not a prefix of %q", namePath.Path()))
}
// The signing key must match the user in parsedName. If the DirEntry
// unpacks correctly, that validates the signing key is that of Writer.
// So, here we validate that Writer is parsedName.User().
if accessEntry.Writer != namePath.User() {
return errors.E(errors.Invalid, accessPath.Path(), namePath.User(), "writer of Access file is not the user of the requested path")
}
return nil
}
// For EE, update the packing for the other readers as specified by the Access file.
// This call, if successful, will replace entry.Name with the value, after any
// link evaluation, from the final call to WhichAccess. The caller may then
// use that name or entry to avoid evaluating the links again.
func (c *Client) addReaders(op errors.Op, entry *upspin.DirEntry, packer upspin.Packer, readers []upspin.UserName) error {
if packer.Packing() != upspin.EEPack {
return nil
}
name := entry.Name
// Add other readers to Packdata.
readersPublicKey := make([]upspin.PublicKey, 0, len(readers)+2)
f := c.config.Factotum()
if f == nil {
return errors.E(op, name, errors.Permission, "no factotum available")
}
readersPublicKey = append(readersPublicKey, f.PublicKey())
all := access.IsAccessControlFile(entry.Name)
for _, r := range readers {
if r == access.AllUsers {
all = true
continue
}
key, err := bind.KeyServer(c.config, c.config.KeyEndpoint())
if err != nil {
return errors.E(op, err)
}
u, err := key.Lookup(r)
if err != nil || len(u.PublicKey) == 0 {
// TODO warn that we can't process one of the readers?
continue
}
if u.PublicKey != readersPublicKey[0] { // don't duplicate self
// TODO(ehg) maybe should check for other duplicates?
readersPublicKey = append(readersPublicKey, u.PublicKey)
}
}
if all {
readersPublicKey = append(readersPublicKey, upspin.AllUsersKey)
}
packdata := make([]*[]byte, 1)
packdata[0] = &entry.Packdata
packer.Share(c.config, readersPublicKey, packdata)
return nil
}
// getReaders returns the list of intended readers for the given name
// according to the Access file.
// If the Access file cannot be read because of lack of permissions,
// it returns the owner of the file (but only if we are not the owner).
func (c *Client) getReaders(op errors.Op, name upspin.PathName, accessEntry *upspin.DirEntry) ([]upspin.UserName, error) {
if accessEntry == nil {
// No Access file present, therefore we must be the owner.
return nil, nil
}
accessData, err := c.Get(accessEntry.Name)
if errors.Is(errors.NotExist, err) || errors.Is(errors.Permission, err) || errors.Is(errors.Private, err) {
// If we failed to get the Access file for access-control
// reasons, then we must not have read access and thus
// cannot know the list of readers.
// Instead, just return the owner as the only reader.
parsed, err := path.Parse(name)
if err != nil {
return nil, err
}
owner := parsed.User()
if owner == c.config.UserName() {
// We are the owner, but the caller always
// adds the us, so return an empty list.
return nil, nil
}
return []upspin.UserName{owner}, nil
} else if err != nil {
// We failed to fetch the Access file for some unexpected reason,
// so bubble the error up.
return nil, err
}
acc, err := access.Parse(accessEntry.Name, accessData)
if err != nil {
return nil, err
}
readers, err := acc.Users(access.Read, c.Get)
if err != nil {
return nil, err
}
return readers, nil
}
func makeDirectoryLookupFn(dir upspin.DirServer, entry *upspin.DirEntry, s *metric.Span) (*upspin.DirEntry, error) {
defer s.StartSpan("dir.makeDirectory").End()
entry.SignedName = entry.Name // Make sure they match as we step through links.
return dir.Put(entry)
}
// MakeDirectory implements upspin.Client.
func (c *Client) MakeDirectory(name upspin.PathName) (*upspin.DirEntry, error) {
const op errors.Op = "client.MakeDirectory"
m, s := newMetric(op)
defer m.Done()
parsed, err := path.Parse(name)
if err != nil {
return nil, errors.E(op, err)
}
entry := &upspin.DirEntry{
Name: parsed.Path(), // SignedName is set in makeDirectoryLookupFn as it needs updating.
Attr: upspin.AttrDirectory,
}
entry, _, err = c.lookup(op, entry, makeDirectoryLookupFn, followFinalLink, s)
return entry, err
}
// Get implements upspin.Client.
func (c *Client) Get(name upspin.PathName) ([]byte, error) {
const op errors.Op = "client.Get"
m, s := newMetric(op)
defer m.Done()
entry, _, err := c.lookup(op, &upspin.DirEntry{Name: name}, lookupLookupFn, followFinalLink, s)
if err != nil {
return nil, errors.E(op, name, err)
}
if entry.IsDir() {
return nil, errors.E(op, name, errors.IsDir)
}
if err = c.validSigner(entry); err != nil {
return nil, errors.E(op, name, err)
}
ss := s.StartSpan("ReadAll")
data, err := clientutil.ReadAll(c.config, entry)
ss.End()
if err != nil {
return nil, errors.E(op, name, err)
}
// Annotate metric with the size retrieved.
// TODO: add location approximation based on IP address?
size, err := entry.Size()
if err != nil {
return nil, err
}
s.SetAnnotation(fmt.Sprintf("bytes=%d", size))
return data, nil
}
func lookupLookupFn(dir upspin.DirServer, entry *upspin.DirEntry, s *metric.Span) (*upspin.DirEntry, error) {
defer s.StartSpan("dir.Lookup").End()
return dir.Lookup(entry.Name)
}
// Lookup implements upspin.Client.
func (c *Client) Lookup(name upspin.PathName, followFinal bool) (*upspin.DirEntry, error) {
const op errors.Op = "client.Lookup"
m, s := newMetric(op)
defer m.Done()
entry, _, err := c.lookup(op, &upspin.DirEntry{Name: name}, lookupLookupFn, followFinal, s)
return entry, err
}
// A lookupFn is called by the evaluation loop in lookup. It calls the underlying
// DirServer operation and may return ErrFollowLink, some other error, or success.
// If it is ErrFollowLink, lookup will step through the link and try again.
type lookupFn func(upspin.DirServer, *upspin.DirEntry, *metric.Span) (*upspin.DirEntry, error)
// lookup returns the DirEntry referenced by the argument entry,
// evaluated by following any links in the path except maybe for one detail:
// The boolean states whether, if the final path element is a link,
// that link should be evaluated. If true, the returned entry represents
// the target of the link. If false, it represents the link itself.
//
// In some cases, such as when called from Lookup, the argument
// entry might contain nothing but a name, but it must always have a name.
// The call may overwrite the fields of the argument DirEntry,
// updating its name as it crosses links.
// The returned DirEntries on success are the result of completing
// the operation followed by the argument to the last successful
// call to fn, which for instance will contain the actual path that
// resulted in a successful call to WhichAccess.
func (c *Client) lookup(op errors.Op, entry *upspin.DirEntry, fn lookupFn, followFinal bool, s *metric.Span) (resultEntry, finalSuccessfulEntry *upspin.DirEntry, err error) {
ss := s.StartSpan("lookup")
defer ss.End()
// As we run, we want to maintain the incoming DirEntry to track the name,
// leaving the rest alone. As the fn will return a newly allocated entry,
// after each link we update the entry to achieve this.
originalName := entry.Name
var prevEntry *upspin.DirEntry
copied := false // Do we need to allocate a new entry to modify its name?
for loop := 0; loop < upspin.MaxLinkHops; loop++ {
parsed, err := path.Parse(entry.Name)
if err != nil {
return nil, nil, errors.E(op, err)
}
dir, err := c.DirServer(parsed.Path())
if err != nil {
return nil, nil, errors.E(op, err)
}
resultEntry, err := fn(dir, entry, ss)
if err == nil {
return resultEntry, entry, nil
}
if prevEntry != nil && errors.Is(errors.NotExist, err) {
return resultEntry, nil, errors.E(op, errors.BrokenLink, prevEntry.Name, err)
}
prevEntry = resultEntry
if err != upspin.ErrFollowLink {
return resultEntry, nil, errors.E(op, originalName, err)
}
// Misbehaving servers could return a nil entry. Handle that explicitly. Issue 451.
if resultEntry == nil {
return nil, nil, errors.E(op, errors.Internal, prevEntry.Name, "server returned nil entry for link")
}
// We have a link.
// First, allocate a new entry if necessary so we don't overwrite user's memory.
if !copied {
tmp := *entry
entry = &tmp
copied = true
}
// Take the prefix of the result entry and substitute that section of the existing name.
parsedResult, err := path.Parse(resultEntry.Name)
if err != nil {
return nil, nil, errors.E(op, err)
}
resultPath := parsedResult.Path()
// The result entry's name must be a prefix of the name we're looking up.
if !strings.HasPrefix(parsed.String(), string(resultPath)) {
return nil, nil, errors.E(op, resultPath, errors.Internal, "link path not prefix")
}
// Update the entry to have the new Name field.
if resultPath == parsed.Path() {
// We're on the last element. We may be done.
if followFinal {
entry.Name = resultEntry.Link
} else {
// Yes, we are done. Return this entry, which is a link.
return resultEntry, entry, nil
}
} else {
entry.Name = path.Join(resultEntry.Link, string(parsed.Path()[len(resultPath):]))
}
}
return nil, nil, errors.E(op, errors.IO, originalName, "link loop")
}
func deleteLookupFn(dir upspin.DirServer, entry *upspin.DirEntry, s *metric.Span) (*upspin.DirEntry, error) {
defer s.StartSpan("dir.Delete").End()
return dir.Delete(entry.Name)
}
// Delete implements upspin.Client.
func (c *Client) Delete(name upspin.PathName) error {
const op errors.Op = "client.Delete"
m, s := newMetric(op)
defer m.Done()
_, _, err := c.lookup(op, &upspin.DirEntry{Name: name}, deleteLookupFn, doNotFollowFinalLink, s)
return err
}
// Glob implements upspin.Client.
func (c *Client) Glob(pattern string) ([]*upspin.DirEntry, error) {
const op errors.Op = "client.Glob"
m, s := newMetric(op)
defer m.Done()
var results []*upspin.DirEntry
var this []string
next := []string{pattern}
for loop := 0; loop < upspin.MaxLinkHops && len(next) > 0; loop++ {
this, next = next, this
next = next[:0]
for _, pattern := range this {
files, links, err := c.globOnePattern(pattern, s)
if err != nil {
first := len(this) == 1 && len(next) == 0
if first || !benignGlobError(err) {
return nil, err
}
continue
}
results = append(results, files...)
if len(links) == 0 {
continue
}
parsed, err := path.Parse(upspin.PathName(pattern))
if err != nil { // Cannot happen, but be careful.
return nil, err
}
for _, link := range links {
// We searched for
// u@g.c/a/*/b
// and have link entry with name
// u@g.c/a/foo
// and target
// v@x.y/d/e/f.
// Replace the pattern that matches the link name
// with the link target and try that the next time:
// v@x.y/d/e/f/b.
linkName, err := path.Parse(link.Name)
if err != nil { // Cannot happen, but be careful.
return nil, err
}
tail := strings.TrimPrefix(parsed.FilePath(),
parsed.First(linkName.NElem()).FilePath())
newPattern := path.Join(link.Link, tail)
next = append(next, string(newPattern))
}
}
}
if len(next) > 0 {
// TODO: Return partial results?
return nil, errors.E(op, upspin.PathName(pattern), "link loop")
}
results = upspin.SortDirEntries(results, true)
return results, nil
}
// benignGlobError reports whether the provided error can be
// safely ignored as part of a multi-request glob operation.
func benignGlobError(err error) bool {
return errors.Is(errors.NotExist, err) ||
errors.Is(errors.Permission, err) ||
errors.Is(errors.Private, err)
}
func (c *Client) globOnePattern(pattern string, s *metric.Span) (entries, links []*upspin.DirEntry, err error) {
defer s.StartSpan("dir.Glob").End()
dir, err := c.DirServer(upspin.PathName(pattern))
if err != nil {
return nil, nil, err
}
entries, err = dir.Glob(pattern)
switch err {
case nil:
return entries, nil, nil
case upspin.ErrFollowLink:
var files, links []*upspin.DirEntry
for _, entry := range entries {
if entry.IsLink() {
links = append(links, entry)
} else {
files = append(files, entry)
}
}
return files, links, nil
default:
return nil, nil, err
}
}
// Create implements upspin.Client.
func (c *Client) Create(name upspin.PathName) (upspin.File, error) {
// TODO: Make sure directory exists?
return file.Writable(c, name), nil
}
// Open implements upspin.Client.
func (c *Client) Open(name upspin.PathName) (upspin.File, error) {
const op errors.Op = "client.Open"
entry, err := c.Lookup(name, followFinalLink)
if err != nil {
return nil, errors.E(op, err)
}
if entry.IsDir() {
return nil, errors.E(op, errors.IsDir, name, "cannot Open a directory")
}
if err = c.validSigner(entry); err != nil {
return nil, errors.E(op, name, err)
}
f, err := file.Readable(c.config, entry)
if err != nil {
return nil, errors.E(op, name, err)
}
return f, nil
}
// DirServer implements upspin.Client.
func (c *Client) DirServer(name upspin.PathName) (upspin.DirServer, error) {
const op errors.Op = "Client.DirServer"
parsed, err := path.Parse(name)
if err != nil {
return nil, errors.E(op, err)
}
dir, err := bind.DirServerFor(c.config, parsed.User())
if err != nil {
return nil, errors.E(op, name, err)
}
return dir, nil
}
// PutDuplicate implements upspin.Client.
// If one of the two files is later modified, the copy and the original will differ.
func (c *Client) PutDuplicate(oldName, newName upspin.PathName) (*upspin.DirEntry, error) {
const op errors.Op = "client.PutDuplicate"
m, s := newMetric(op)
defer m.Done()
return c.dupOrRename(op, oldName, newName, false, s)
}
// Rename implements upspin.Client.
func (c *Client) Rename(oldName, newName upspin.PathName) (*upspin.DirEntry, error) {
const op errors.Op = "client.Rename"
m, s := newMetric(op)
defer m.Done()
return c.dupOrRename(op, oldName, newName, true, s)
}
// SetTime implements upspin.Client.
func (c *Client) SetTime(name upspin.PathName, t upspin.Time) error {
const op errors.Op = "client.SetTime"
m, s := newMetric(op)
defer m.Done()
entry, _, err := c.lookup(op, &upspin.DirEntry{Name: name}, lookupLookupFn, doNotFollowFinalLink, s)
if err != nil {
return errors.E(op, err)
}
packer := pack.Lookup(entry.Packing)
if packer == nil {
return errors.E(op, name, errors.Invalid, errors.Errorf("unrecognized Packing %d", c.config.Packing()))
}
if err := packer.SetTime(c.config, entry, t); err != nil {
return errors.E(op, err)
}
// Record directory entry.
_, _, err = c.lookup(op, entry, putLookupFn, doNotFollowFinalLink, s)
if err != nil {
return errors.E(op, err)
}
return nil
}
func (c *Client) dupOrRename(op errors.Op, oldName, newName upspin.PathName, rename bool, s *metric.Span) (*upspin.DirEntry, error) {
entry, _, err := c.lookup(op, &upspin.DirEntry{Name: oldName}, lookupLookupFn, doNotFollowFinalLink, s)
if err != nil {
return nil, err
}
if entry.IsDir() {
return nil, errors.E(op, oldName, errors.IsDir, "cannot link or rename directories")
}
trueOldName := entry.Name
packer := pack.Lookup(entry.Packing)
if packer == nil {
return nil, errors.E(op, oldName, errors.Invalid, errors.Errorf("unrecognized Packing %d", entry.Packing))
}
if access.IsAccessControlFile(newName) {
return nil, errors.E(op, newName, errors.Invalid, "Access or Group files cannot be renamed")
}
// Update the directory entry with the new name and sequence.
// We insist the new file must not exist.
entry.Sequence = upspin.SeqNotExist
if err := packer.Name(c.config, entry, newName); err != nil {
return nil, err
}
// Rewrap reader keys only if changing directory.
// This could be cheaper (just compare the prefix), but it's clear and correct as written.
newParsed, err := path.Parse(entry.Name)
if err != nil {
return nil, errors.E(op, err)
}
oldParsed, err := path.Parse(trueOldName)
if err != nil {
return nil, errors.E(op, err)
}
if !oldParsed.Drop(1).Equal(newParsed.Drop(1)) {
accessEntry, _, err := c.lookup(op, entry, whichAccessLookupFn, doNotFollowFinalLink, s)
if err != nil {
return nil, errors.E(op, trueOldName, err)
}
readers, err := c.getReaders(op, trueOldName, accessEntry)
if err != nil {
return nil, errors.E(op, trueOldName, err)
}
if err := c.addReaders(op, entry, packer, readers); err != nil {
return nil, errors.E(trueOldName, err)
}
}
// Record directory entry.
entry, _, err = c.lookup(op, entry, putLookupFn, doNotFollowFinalLink, s)
if err != nil {
return nil, err
}
if rename {
// Remove original entry. We have all we need here and we know it's not a link.
oldDir, err := c.DirServer(trueOldName)
if err != nil {
return nil, errors.E(op, err)
}
if _, err := oldDir.Delete(trueOldName); err != nil {
return entry, err
}
}
return entry, nil
}
func newMetric(op errors.Op) (*metric.Metric, *metric.Span) {
m := metric.New("")
s := m.StartSpan(op).SetKind(metric.Client)
return m, s
}