blob: f6a01e4ffc39a92ca07c5bc62f9e2838d46b10bc [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 filesystem provides an upspin.DirServer
// that serves files from a local file system.
// It must be used in conjunction with the upspin.StoreServer
// implementation in package upspin.io/store/filesystem.
package filesystem
import (
"io/ioutil"
"os"
"path/filepath"
"strings"
"upspin.io/access"
"upspin.io/errors"
"upspin.io/log"
"upspin.io/pack"
"upspin.io/path"
"upspin.io/upspin"
)
// TODO: use EEIntegrityPack
const packing = upspin.PlainPack
type server struct {
// Set by New.
root string
server upspin.Config
defaultAccess *access.Access
// Set by Dial.
user upspin.Config
}
// New creates a new DirServer instance with the
// provided server configuration and options.
// The only valid configuration option is "root", which
// specifies a path to the file system root.
func New(cfg upspin.Config, options ...string) (upspin.DirServer, error) {
const op = "dir/filesystem.New"
s := &server{server: cfg}
var err error
s.root, s.defaultAccess, err = newRoot(cfg, options)
if err != nil {
return nil, errors.E(op, err)
}
return s, nil
}
// verifyUserRoot checks that the user name in the path is the owner of this root.
func (s *server) verifyUserRoot(parsed path.Parsed) error {
if parsed.User() != s.server.UserName() {
return errors.E(errors.Invalid, parsed.Path(), errors.Errorf("mismatched user name %q", parsed.User()))
}
return nil
}
func (s *server) Lookup(pathName upspin.PathName) (*upspin.DirEntry, error) {
const op = "dir/filesystem.Lookup"
log.Println(op, pathName)
parsed, err := path.Parse(pathName)
if err != nil {
return nil, errors.E(op, err)
}
if err := s.verifyUserRoot(parsed); err != nil {
return nil, errors.E(op, err)
}
if ok, err := can(s.root, s.defaultAccess, s.user.UserName(), access.List, parsed); err != nil {
return nil, errors.E(op, err)
} else if !ok {
return nil, errors.E(op, access.ErrPermissionDenied)
}
e, err := s.entry(filepath.Join(s.root, parsed.FilePath()))
if err != nil {
return nil, errors.E(op, err)
}
return e, nil
}
// entry returns the DirEntry for the named local file or directory.
func (s *server) entry(file string) (*upspin.DirEntry, error) {
// TODO: cache DirEntries and expire them based on the file's
// modification time.
f, err := os.Open(file)
if err != nil {
return nil, err
}
defer f.Close()
info, err := f.Stat()
if err != nil {
return nil, err
}
attr := upspin.AttrNone
if info.IsDir() {
attr = upspin.AttrDirectory
}
// TODO(adg): handle symbolic links
if !strings.HasPrefix(file, s.root) {
return nil, errors.Str("internal error: not in root")
}
name := s.upspinPathFromLocal(file)
entry := upspin.DirEntry{
Name: name,
SignedName: name,
Packing: packing,
Time: upspin.TimeFromGo(info.ModTime()),
Attr: attr,
Sequence: 0,
Writer: s.server.UserName(), // TODO: Is there a better answer?
}
if !info.IsDir() {
p := pack.Lookup(packing)
bp, err := p.Pack(s.server, &entry)
if err != nil {
return nil, err
}
contents, err := ioutil.ReadAll(f)
if err != nil {
return nil, err
}
// Ignore the returned "ciphertext", as using the plain packer
// it is equivalent to the cleartext.
_, err = bp.Pack(contents)
if err != nil {
return nil, err
}
bp.SetLocation(upspin.Location{
Endpoint: s.server.StoreEndpoint(),
Reference: upspin.Reference(file[len(s.root):]),
})
if err := bp.Close(); err != nil {
return nil, err
}
}
return &entry, nil
}
// upspinPathFromLocal returns the upspin.PathName for
// the given absolute local path name.
func (s *server) upspinPathFromLocal(local string) upspin.PathName {
return upspin.PathName(s.server.UserName()) + "/" + upspin.PathName(local[len(s.root):])
}
func (s *server) Glob(pattern string) ([]*upspin.DirEntry, error) {
const op = "dir/filesystem.Glob"
log.Println(op, pattern)
parsed, err := path.Parse(upspin.PathName(pattern))
if err != nil {
return nil, errors.E(op, err)
}
if err := s.verifyUserRoot(parsed); err != nil {
return nil, errors.E(op, err)
}
var (
matches []string
next = []string{s.root}
)
for i := 0; i < parsed.NElem(); i++ {
elem := parsed.Elem(i)
matches, next = next, matches[:0]
for _, match := range matches {
if isGlobPattern(elem) || i == parsed.NElem()-1 {
parsed, err := path.Parse(s.upspinPathFromLocal(match))
if err != nil {
return nil, errors.E(op, err)
}
if ok, err := can(s.root, s.defaultAccess, s.user.UserName(), access.List, parsed); err != nil {
return nil, errors.E(op, err)
} else if !ok {
continue
}
}
names, err := filepath.Glob(filepath.Join(match, elem))
// TODO(r): remove this error check
if err != nil {
return nil, errors.E(op, err)
}
next = append(next, names...)
}
}
matches = next
var entries []*upspin.DirEntry
for _, match := range matches {
e, err := s.entry(match)
if err != nil {
return nil, errors.E(op, err)
}
parsed, err := path.Parse(upspin.PathName(s.upspinPathFromLocal(match)))
if err != nil {
return nil, errors.E(op, err)
}
if ok, err := can(s.root, s.defaultAccess, s.user.UserName(), access.Read, parsed); err != nil {
return nil, errors.E(op, err)
} else if !ok {
e.Blocks = nil
e.Packdata = nil
}
entries = append(entries, e)
}
return entries, nil
}
// isGlobPattern replies whether the given path element
// contains a glob pattern.
func isGlobPattern(elem string) bool {
return strings.ContainsAny(elem, `*?[]`)
}
func (s *server) WhichAccess(pathName upspin.PathName) (*upspin.DirEntry, error) {
const op = "dir/filesystem.WhichAccess"
log.Println(op, pathName)
parsed, err := path.Parse(pathName)
if err != nil {
return nil, errors.E(op, err)
}
err = s.verifyUserRoot(parsed)
if err != nil {
return nil, errors.E(op, err)
}
if ok, err := can(s.root, s.defaultAccess, s.user.UserName(), access.AnyRight, parsed); err != nil {
return nil, errors.E(op, err)
} else if !ok {
return nil, errors.E(op, access.ErrPermissionDenied)
}
accessPath, err := whichAccess(s.root, parsed)
if err != nil {
return nil, errors.E(op, err)
}
e, err := s.entry(string(accessPath))
if err != nil {
return nil, errors.E(op, err)
}
return e, nil
}
// Watch implements upspin.DirServer.
func (d *server) Watch(upspin.PathName, int64, <-chan struct{}) (<-chan upspin.Event, error) {
return nil, upspin.ErrNotSupported
}
func (s *server) Ping() bool {
return true
}
func (s *server) Close() {
}
func (s *server) Dial(cfg upspin.Config, e upspin.Endpoint) (upspin.Service, error) {
const op = "dir/filesystem.Dial"
dialed := *s
dialed.user = cfg
return &dialed, nil
}
// Methods that are not implemented.
var errReadOnly = errors.Str("read-only name space")
func (s *server) Delete(pathName upspin.PathName) (*upspin.DirEntry, error) {
const op = "dir/filesystem.Delete"
return nil, errors.E(op, errReadOnly)
}
func (s *server) Put(entry *upspin.DirEntry) (*upspin.DirEntry, error) {
const op = "dir/filesystem.Put"
return nil, errors.E(op, errReadOnly)
}
// Methods that do not apply to this server.
func (s *server) Endpoint() upspin.Endpoint {
return upspin.Endpoint{} // No endpoint.
}