Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(482)

Unified Diff: go/src/infra/tools/cipd/local/fs.go

Issue 1154423015: cipd: Extract high level file system operations into the separate interface. (Closed) Base URL: https://chromium.googlesource.com/infra/infra.git@master
Patch Set: extract-cipd-fs Created 5 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: go/src/infra/tools/cipd/local/fs.go
diff --git a/go/src/infra/tools/cipd/local/fs.go b/go/src/infra/tools/cipd/local/fs.go
new file mode 100644
index 0000000000000000000000000000000000000000..1e11f3053cc71adb92bb5d0781f39262dd305e93
--- /dev/null
+++ b/go/src/infra/tools/cipd/local/fs.go
@@ -0,0 +1,259 @@
+// Copyright 2015 The Chromium 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 local
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+ "strings"
+ "sync"
+ "time"
+
+ "github.com/luci/luci-go/common/logging"
+)
+
+// FileSystem is a high level interface for operations that touch single file
nodir 2015/06/03 19:30:27 nit: high-level
Vadim Sh. 2015/06/03 22:35:08 Done.
+// system partition. All functions operate in terms of native file paths. It
+// exists mostly to hide difference between file system semantic on Windows and
+// Linux\Mac.
+type FileSystem interface {
+ // Root returns absolute path to a directory FileSystem operates in. All FS
+ // actions are restricted to this directory.
+ Root() string
+
+ // ToAbsPath converts a relative path to an absolute one and verifies it is
+ // under the root path of the FileSystem object.
+ ToAbsPath(path string) (string, error)
+
+ // EnsureDirectory creates a directory at given native path if it doesn't
+ // exist yet. It takes an absolute path or a path relative to the current
+ // working directory and always returns absolute path.
+ EnsureDirectory(path string) (string, error)
+
+ // EnsureSymlink creates a symlink pointing to a target. It will create full
+ // directory path to the symlink if necessary.
+ EnsureSymlink(path string, target string) error
+
+ // EnsureFileGone removes a file, logging the errors (if any). Missing file is
+ // not an error.
+ EnsureFileGone(path string) error
+
+ // EnsureDirectoryGone recursively removes a directory.
+ EnsureDirectoryGone(path string) error
+
+ // Renames oldpath to newpath. If newpath already exists (be it a file or a
+ // directory), removes it first.
+ Replace(oldpath, newpath string) error
+}
+
+// NewFileSystem returns default FileSystem implementation that operates with
+// files under a given path. All methods accept absolute paths or paths relative
+// to current working directory. FileSystem will ensure they are under 'root'
+// directory.
+func NewFileSystem(root string, logger logging.Logger) FileSystem {
+ if logger == nil {
+ logger = logging.Null()
+ }
+ abs, err := filepath.Abs(root)
+ if err != nil {
+ return &fsImplErr{err}
+ }
+ return &fsImpl{abs, logger}
+}
+
+// fsImplErr implements FileSystem by returning given error from all methods.
+type fsImplErr struct {
+ err error
+}
+
+// Root returns absolute path to a directory FileSystem operates in. All FS
+// actions are restricted to this directory.
+func (f *fsImplErr) Root() string { return "" }
+func (f *fsImplErr) ToAbsPath(path string) (string, error) { return "", f.err }
+func (f *fsImplErr) EnsureDirectory(path string) (string, error) { return "", f.err }
+func (f *fsImplErr) EnsureSymlink(path string, target string) error { return f.err }
+func (f *fsImplErr) EnsureFileGone(path string) error { return f.err }
+func (f *fsImplErr) EnsureDirectoryGone(path string) error { return f.err }
+func (f *fsImplErr) Replace(oldpath, newpath string) error { return f.err }
+
+/// Implementation.
+
+type fsImpl struct {
+ root string
+ logger logging.Logger
+}
+
+func (f *fsImpl) Root() string {
+ return f.root
+}
+
+func (f *fsImpl) ToAbsPath(p string) (string, error) {
+ p, err := filepath.Abs(p)
+ if err != nil {
+ return "", err
+ }
+ rel, err := filepath.Rel(f.root, p)
+ if err != nil {
+ return "", err
+ }
+ rel = filepath.ToSlash(rel)
+ if rel == ".." || strings.HasPrefix(rel, "../") {
+ return "", fmt.Errorf("fs: path %s is outside of %s", p, f.root)
+ }
+ return p, nil
+}
+
+func (f *fsImpl) EnsureDirectory(path string) (string, error) {
+ path, err := f.ToAbsPath(path)
+ if err != nil {
+ return "", err
+ }
+ // MkdirAll returns nil if path already exists.
+ err = os.MkdirAll(path, 0777)
+ if err == nil {
nodir 2015/06/03 19:30:27 nit: join lines 115-116?
Vadim Sh. 2015/06/03 22:35:08 Done.
+ return path, nil
+ }
+ return "", err
+}
+
+func (f *fsImpl) EnsureSymlink(path string, target string) error {
+ path, err := f.ToAbsPath(path)
+ if err != nil {
+ return err
+ }
+ if existing, _ := os.Readlink(path); existing == target {
+ return nil
+ }
+ if _, err := f.EnsureDirectory(filepath.Dir(path)); err != nil {
+ return err
+ }
+
+ // Create a new symlink file, can't modify existing one in place.
+ temp := fmt.Sprintf("%s_%s", path, pseudoRand())
+ if err := os.Symlink(target, temp); err != nil {
+ return err
+ }
+
+ // Atomically replace a current symlink with a new one.
nodir 2015/06/03 19:30:27 use "the" instead of a
Vadim Sh. 2015/06/03 22:35:08 Done.
+ err = os.Rename(temp, path)
+ if err != nil {
+ os.Remove(temp)
nodir 2015/06/03 19:30:27 use defer for this kind of stuff
Vadim Sh. 2015/06/03 22:35:08 In this case defer is not good, since cleanup need
+ return err
+ }
+
+ return nil
+}
+
+func (f *fsImpl) EnsureFileGone(path string) error {
+ path, err := f.ToAbsPath(path)
+ if err != nil {
+ return err
+ }
+ err = os.Remove(path)
+ if err != nil && !os.IsNotExist(err) {
+ f.logger.Warningf("fs: failed to remove %s - %s", path, err)
+ return err
+ }
+ return nil
+}
+
+func (f *fsImpl) EnsureDirectoryGone(path string) error {
+ path, err := f.ToAbsPath(path)
+ if err != nil {
+ return err
+ }
+ // Make directory "disappear" instantly by renaming it first.
+ temp := fmt.Sprintf("%s_%v", path, pseudoRand())
+ if err = os.Rename(path, temp); err != nil {
+ if os.IsNotExist(err) {
+ return nil
+ }
+ f.logger.Warningf("fs: failed to rename directory %s - %s", path, err)
+ return err
+ }
+ if err = os.RemoveAll(temp); err != nil {
+ f.logger.Warningf("fs: failed to remove directory %s - %s", temp, err)
+ return err
+ }
+ return nil
+}
+
+func (f *fsImpl) Replace(oldpath, newpath string) error {
+ oldpath, err := f.ToAbsPath(oldpath)
+ if err != nil {
+ return err
+ }
+ newpath, err = f.ToAbsPath(newpath)
+ if err != nil {
+ return err
+ }
+ if oldpath == newpath {
+ return nil
+ }
+
+ // Make sure oldpath exists before doing heavy stuff.
+ if _, err = os.Stat(oldpath); err != nil {
+ return err
+ }
+
+ // Make parent directory of newpath.
+ if _, err = f.EnsureDirectory(filepath.Dir(newpath)); err != nil {
+ return err
+ }
+
+ // Try a regular move first. Replaces files and empty directories.
+ if err = os.Rename(oldpath, newpath); err == nil {
+ return nil
+ }
+
+ // Move existing path away, if it is there.
+ temp := fmt.Sprintf("%s_%s", newpath, pseudoRand())
+ if err = os.Rename(newpath, temp); err != nil {
+ if !os.IsNotExist(err) {
+ f.logger.Warningf("fs: failed to rename(%v, %v) - %s", newpath, temp, err)
+ return err
+ }
+ temp = ""
+ }
+
+ // 'newpath' now should be available.
+ if err := os.Rename(oldpath, newpath); err != nil {
+ f.logger.Warningf("fs: failed to rename(%v, %v) - %s", oldpath, newpath, err)
+ // Try to return the path back... May be too late already.
+ if temp != "" {
+ if err := os.Rename(temp, newpath); err != nil {
+ f.logger.Errorf("fs: failed to rename(%v, %v) after unsuccessful move - %s", temp, newpath, err)
+ }
+ }
+ return err
+ }
+
+ // Cleanup the garbage left. Not a error if fails.
+ if temp != "" {
+ if err := f.EnsureDirectoryGone(temp); err != nil {
nodir 2015/06/03 19:30:27 use defer for cleanup
nodir 2015/06/03 19:30:27 no need for err variable here
Vadim Sh. 2015/06/03 22:35:08 Why?.. It is logged.
Vadim Sh. 2015/06/03 22:35:08 again, cleanup path is different depending on circ
nodir 2015/06/04 00:57:36 Acknowledged.
+ f.logger.Warningf("fs: failed to cleanup garbage after file replace - %s", err)
+ }
+ }
+ return nil
+}
+
+/// Internal stuff.
+
+var lastUsedTime int64
+var lastUsedTimeLock sync.Mutex
+
+// pseudoRand returns "random enough" string that can be used in file system
+// paths of temp files.
+func pseudoRand() string {
+ lastUsedTimeLock.Lock()
+ ts := time.Now().UnixNano()
+ if ts == lastUsedTime {
nodir 2015/06/03 19:30:27 if ts <= lastUsedTime { ts = lastUsedTime + 1 }
Vadim Sh. 2015/06/03 22:35:08 Done.
+ ts++
+ }
+ lastUsedTime = ts
+ lastUsedTimeLock.Unlock()
nodir 2015/06/03 19:30:27 You are not doing defer because Getpid is slow? If
Vadim Sh. 2015/06/03 22:35:08 I'm not using a defer because 4 lines of code is n
nodir 2015/06/04 00:57:36 in general, defers are not syntax sugar. If someth
+ return fmt.Sprintf("%v_%v", os.Getpid(), ts)
+}

Powered by Google App Engine
This is Rietveld 408576698