| // 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 |
| C string // Creator |
| } |
| |
| // 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 { |
| n.creator = e.C |
| 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.creator, 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, creator string, vPerms *VersionedPermissions) error { |
| s.l.Lock() |
| defer s.l.Unlock() |
| e := storeElement{N: name, V: *vPerms, C: creator} |
| 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() |
| } |