blob: 1dfd2f89f0533c08eb70adb66762309441c6e339 [file] [log] [blame]
// Copyright 2015 The Vanadium 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 mounttablelib
import (
"encoding/json"
"io"
"os"
"path"
"strings"
"sync"
"v.io/x/lib/vlog"
)
type store struct {
l sync.Mutex
mt *mountTable
dir string
enc *json.Encoder
f *os.File
}
type storeElement struct {
N string // Name of affected node
V VersionedPermissions
D bool // True if the subtree at N has been deleted
}
// newPersistentStore will read the permissions log from the directory and apply them to the
// in memory tree. It will then write a new file from the in memory tree and any new permission
// changes will be appened to this file. By writing into a new file, we effectively compress
// the permissions file since any set permissions that have been deleted or overwritten will be
// lost.
//
// The code manages three files in the directory 'dir':
// persistent.permslog - the log of permissions. A new log entry is added with each SetPermissions or
// Delete RPC.
// tmp.permslog - a temporary file created whenever we restart. Once we write the current state into it,
// it will be renamed persistent.perms becoming the new log.
// old.permslog - the previous version of persistent.perms. This is left around primarily for debugging
// and as an emergency backup.
func newPersistentStore(mt *mountTable, dir string) persistence {
s := &store{mt: mt, dir: dir}
file := path.Join(dir, "persistent.permslog")
tmp := path.Join(dir, "tmp.permslog")
old := path.Join(dir, "old.permslog")
// If the permissions file doesn't exist, try renaming the temporary one.
f, err := os.Open(file)
if err != nil {
if !os.IsNotExist(err) {
vlog.Fatalf("cannot open %s: %s", file, err)
}
os.Rename(tmp, file)
if f, err = os.Open(file); err != nil && !os.IsNotExist(err) {
vlog.Fatalf("cannot open %s: %s", file, err)
}
} else {
os.Remove(tmp)
}
// Parse the permissions file and apply it to the in memory tree.
if f != nil {
if err := s.parseLogFile(f); err != nil {
f.Close()
// Log the error but keep going. There's not much else we can do.
vlog.Infof("parsing old persistent permissions file %s: %s", file, err)
}
f.Close()
}
// Write the permissions to a temporary file. This compresses
// the file since it writes out only the end state.
f, err = os.OpenFile(tmp, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0600)
if err != nil {
// Log the error but keep going, don't compress, just append to the current file.
vlog.Infof("can't rewrite persistent permissions file %s: %s", file, err)
if f, err = os.OpenFile(file, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0600); err != nil {
vlog.Fatalf("can't append to log %s: %s", file, err)
}
f.Seek(0, 2)
s.enc = json.NewEncoder(f)
return s
}
s.enc = json.NewEncoder(f)
s.depthFirstPersist(mt.root, "")
f.Close()
// Switch names and remove the old file.
if err := os.Remove(old); err != nil {
vlog.Infof("removing %s: %s", old, err)
}
if err := os.Rename(file, old); err != nil {
vlog.Infof("renaming %s to %s: %s", file, old, err)
}
if err := os.Rename(tmp, file); err != nil {
vlog.Fatalf("renaming %s to %s: %s", tmp, file, err)
}
// Reopen the new log file. We could have just kept around the encoder used
// to create it but that assumes that, after the Rename above, the s.f still
// points to the same file. Only true on Unix like file systems.
f, err = os.OpenFile(file, os.O_WRONLY|os.O_APPEND, 0600)
if err != nil {
vlog.Fatalf("can't open %s: %s", file, err)
}
f.Seek(0, 2)
s.enc = json.NewEncoder(f)
return s
}
// parseLogFile reads a file and parses the contained VersionedPermissions .
func (s *store) parseLogFile(f *os.File) error {
if f == nil {
return nil
}
vlog.VI(2).Infof("parseLogFile(%s)", f.Name())
mt := s.mt
decoder := json.NewDecoder(f)
for {
var e storeElement
if err := decoder.Decode(&e); err != nil {
if err == io.EOF {
break
}
return err
}
elems := strings.Split(e.N, "/")
n, err := mt.findNode(nil, nil, elems, true, nil)
if n != nil || err == nil {
if e.D {
mt.deleteNode(n.parent, elems[len(elems)-1])
vlog.VI(2).Infof("deleted %s", e.N)
} else {
n.vPerms = &e.V
n.explicitPermissions = true
vlog.VI(2).Infof("added versions permissions %v to %s", e.V, e.N)
}
}
n.parent.Unlock()
n.Unlock()
}
return nil
}
// depthFirstPersist performs a recursive depth first traversal logging any explicit permissions.
// Doing this immediately after reading in a log file effectively compresses the log file since
// any duplicate or deleted entries disappear.
func (s *store) depthFirstPersist(n *node, name string) {
if n.explicitPermissions {
s.persistPerms(name, n.vPerms)
}
for nodeName, c := range n.children {
s.depthFirstPersist(c, path.Join(name, nodeName))
}
}
// persistPerms appends a changed permission to the log.
func (s *store) persistPerms(name string, vPerms *VersionedPermissions) error {
s.l.Lock()
defer s.l.Unlock()
e := storeElement{N: name, V: *vPerms}
return s.enc.Encode(&e)
}
// persistDelete appends a single deletion to the log.
func (s *store) persistDelete(name string) error {
s.l.Lock()
defer s.l.Unlock()
e := storeElement{N: name, D: true}
return s.enc.Encode(&e)
}
func (s *store) close() {
s.f.Close()
}