OLD | NEW |
(Empty) | |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 package local |
| 6 |
| 7 import ( |
| 8 "fmt" |
| 9 "os" |
| 10 "path/filepath" |
| 11 "strings" |
| 12 "sync" |
| 13 "time" |
| 14 |
| 15 "github.com/luci/luci-go/common/logging" |
| 16 ) |
| 17 |
| 18 // FileSystem is a high-level interface for operations that touch single file |
| 19 // system subpath. All functions operate in terms of native file paths. It |
| 20 // exists mostly to hide differences between file system semantic on Windows and |
| 21 // Linux\Mac. |
| 22 type FileSystem interface { |
| 23 // Root returns absolute path to a directory FileSystem operates in. All
FS |
| 24 // actions are restricted to this directory. |
| 25 Root() string |
| 26 |
| 27 // ToAbsPath converts a relative path to an absolute one and verifies it
is |
| 28 // under the root path of the FileSystem object. |
| 29 ToAbsPath(path string) (string, error) |
| 30 |
| 31 // EnsureDirectory creates a directory at given native path if it doesn'
t |
| 32 // exist yet. It takes an absolute path or a path relative to the curren
t |
| 33 // working directory and always returns absolute path. |
| 34 EnsureDirectory(path string) (string, error) |
| 35 |
| 36 // EnsureSymlink creates a symlink pointing to a target. It will create
full |
| 37 // directory path to the symlink if necessary. |
| 38 EnsureSymlink(path string, target string) error |
| 39 |
| 40 // EnsureFileGone removes a file, logging the errors (if any). Missing f
ile is |
| 41 // not an error. |
| 42 EnsureFileGone(path string) error |
| 43 |
| 44 // EnsureDirectoryGone recursively removes a directory. |
| 45 EnsureDirectoryGone(path string) error |
| 46 |
| 47 // Renames oldpath to newpath. If newpath already exists (be it a file o
r a |
| 48 // directory), removes it first. |
| 49 Replace(oldpath, newpath string) error |
| 50 } |
| 51 |
| 52 // NewFileSystem returns default FileSystem implementation that operates with |
| 53 // files under a given path. All methods accept absolute paths or paths relative |
| 54 // to current working directory. FileSystem will ensure they are under 'root' |
| 55 // directory. |
| 56 func NewFileSystem(root string, logger logging.Logger) FileSystem { |
| 57 if logger == nil { |
| 58 logger = logging.Null() |
| 59 } |
| 60 abs, err := filepath.Abs(root) |
| 61 if err != nil { |
| 62 return &fsImplErr{err} |
| 63 } |
| 64 return &fsImpl{abs, logger} |
| 65 } |
| 66 |
| 67 // fsImplErr implements FileSystem by returning given error from all methods. |
| 68 type fsImplErr struct { |
| 69 err error |
| 70 } |
| 71 |
| 72 // Root returns absolute path to a directory FileSystem operates in. All FS |
| 73 // actions are restricted to this directory. |
| 74 func (f *fsImplErr) Root() string { return ""
} |
| 75 func (f *fsImplErr) ToAbsPath(path string) (string, error) { return "",
f.err } |
| 76 func (f *fsImplErr) EnsureDirectory(path string) (string, error) { return "",
f.err } |
| 77 func (f *fsImplErr) EnsureSymlink(path string, target string) error { return f.e
rr } |
| 78 func (f *fsImplErr) EnsureFileGone(path string) error { return f.e
rr } |
| 79 func (f *fsImplErr) EnsureDirectoryGone(path string) error { return f.e
rr } |
| 80 func (f *fsImplErr) Replace(oldpath, newpath string) error { return f.e
rr } |
| 81 |
| 82 /// Implementation. |
| 83 |
| 84 type fsImpl struct { |
| 85 root string |
| 86 logger logging.Logger |
| 87 } |
| 88 |
| 89 func (f *fsImpl) Root() string { |
| 90 return f.root |
| 91 } |
| 92 |
| 93 func (f *fsImpl) ToAbsPath(p string) (string, error) { |
| 94 p, err := filepath.Abs(p) |
| 95 if err != nil { |
| 96 return "", err |
| 97 } |
| 98 rel, err := filepath.Rel(f.root, p) |
| 99 if err != nil { |
| 100 return "", err |
| 101 } |
| 102 rel = filepath.ToSlash(rel) |
| 103 if rel == ".." || strings.HasPrefix(rel, "../") { |
| 104 return "", fmt.Errorf("fs: path %s is outside of %s", p, f.root) |
| 105 } |
| 106 return p, nil |
| 107 } |
| 108 |
| 109 func (f *fsImpl) EnsureDirectory(path string) (string, error) { |
| 110 path, err := f.ToAbsPath(path) |
| 111 if err != nil { |
| 112 return "", err |
| 113 } |
| 114 // MkdirAll returns nil if path already exists. |
| 115 if err = os.MkdirAll(path, 0777); err != nil { |
| 116 return "", err |
| 117 } |
| 118 return path, nil |
| 119 } |
| 120 |
| 121 func (f *fsImpl) EnsureSymlink(path string, target string) error { |
| 122 path, err := f.ToAbsPath(path) |
| 123 if err != nil { |
| 124 return err |
| 125 } |
| 126 if existing, _ := os.Readlink(path); existing == target { |
| 127 return nil |
| 128 } |
| 129 if _, err := f.EnsureDirectory(filepath.Dir(path)); err != nil { |
| 130 return err |
| 131 } |
| 132 |
| 133 // Create a new symlink file, can't modify existing one in place. |
| 134 temp := fmt.Sprintf("%s_%s", path, pseudoRand()) |
| 135 if err := os.Symlink(target, temp); err != nil { |
| 136 return err |
| 137 } |
| 138 |
| 139 // Atomically replace the current symlink with a new one. |
| 140 if err := os.Rename(temp, path); err != nil { |
| 141 err2 := os.Remove(temp) |
| 142 if err2 != nil { |
| 143 f.logger.Warningf("fs: failed to remove %s - %s", temp,
err2) |
| 144 } |
| 145 return err |
| 146 } |
| 147 |
| 148 return nil |
| 149 } |
| 150 |
| 151 func (f *fsImpl) EnsureFileGone(path string) error { |
| 152 path, err := f.ToAbsPath(path) |
| 153 if err != nil { |
| 154 return err |
| 155 } |
| 156 err = os.Remove(path) |
| 157 if err != nil && !os.IsNotExist(err) { |
| 158 f.logger.Warningf("fs: failed to remove %s - %s", path, err) |
| 159 return err |
| 160 } |
| 161 return nil |
| 162 } |
| 163 |
| 164 func (f *fsImpl) EnsureDirectoryGone(path string) error { |
| 165 path, err := f.ToAbsPath(path) |
| 166 if err != nil { |
| 167 return err |
| 168 } |
| 169 // Make directory "disappear" instantly by renaming it first. |
| 170 temp := fmt.Sprintf("%s_%v", path, pseudoRand()) |
| 171 if err = os.Rename(path, temp); err != nil { |
| 172 if os.IsNotExist(err) { |
| 173 return nil |
| 174 } |
| 175 f.logger.Warningf("fs: failed to rename directory %s - %s", path
, err) |
| 176 return err |
| 177 } |
| 178 if err = os.RemoveAll(temp); err != nil { |
| 179 f.logger.Warningf("fs: failed to remove directory %s - %s", temp
, err) |
| 180 return err |
| 181 } |
| 182 return nil |
| 183 } |
| 184 |
| 185 func (f *fsImpl) Replace(oldpath, newpath string) error { |
| 186 oldpath, err := f.ToAbsPath(oldpath) |
| 187 if err != nil { |
| 188 return err |
| 189 } |
| 190 newpath, err = f.ToAbsPath(newpath) |
| 191 if err != nil { |
| 192 return err |
| 193 } |
| 194 if oldpath == newpath { |
| 195 return nil |
| 196 } |
| 197 |
| 198 // Make sure oldpath exists before doing heavy stuff. |
| 199 if _, err = os.Stat(oldpath); err != nil { |
| 200 return err |
| 201 } |
| 202 |
| 203 // Make parent directory of newpath. |
| 204 if _, err = f.EnsureDirectory(filepath.Dir(newpath)); err != nil { |
| 205 return err |
| 206 } |
| 207 |
| 208 // Try a regular move first. Replaces files and empty directories. |
| 209 if err = os.Rename(oldpath, newpath); err == nil { |
| 210 return nil |
| 211 } |
| 212 |
| 213 // Move existing path away, if it is there. |
| 214 temp := fmt.Sprintf("%s_%s", newpath, pseudoRand()) |
| 215 if err = os.Rename(newpath, temp); err != nil { |
| 216 if !os.IsNotExist(err) { |
| 217 f.logger.Warningf("fs: failed to rename(%v, %v) - %s", n
ewpath, temp, err) |
| 218 return err |
| 219 } |
| 220 temp = "" |
| 221 } |
| 222 |
| 223 // 'newpath' now should be available. |
| 224 if err := os.Rename(oldpath, newpath); err != nil { |
| 225 f.logger.Warningf("fs: failed to rename(%v, %v) - %s", oldpath,
newpath, err) |
| 226 // Try to return the path back... May be too late already. |
| 227 if temp != "" { |
| 228 if err := os.Rename(temp, newpath); err != nil { |
| 229 f.logger.Errorf("fs: failed to rename(%v, %v) af
ter unsuccessful move - %s", temp, newpath, err) |
| 230 } |
| 231 } |
| 232 return err |
| 233 } |
| 234 |
| 235 // Cleanup the garbage left. Not a error if fails. |
| 236 if temp != "" { |
| 237 if err := f.EnsureDirectoryGone(temp); err != nil { |
| 238 f.logger.Warningf("fs: failed to cleanup garbage after f
ile replace - %s", err) |
| 239 } |
| 240 } |
| 241 return nil |
| 242 } |
| 243 |
| 244 /// Internal stuff. |
| 245 |
| 246 var ( |
| 247 lastUsedTime int64 |
| 248 lastUsedTimeLock sync.Mutex |
| 249 ) |
| 250 |
| 251 // pseudoRand returns "random enough" string that can be used in file system |
| 252 // paths of temp files. |
| 253 func pseudoRand() string { |
| 254 ts := time.Now().UnixNano() |
| 255 lastUsedTimeLock.Lock() |
| 256 if ts <= lastUsedTime { |
| 257 ts = lastUsedTime + 1 |
| 258 } |
| 259 lastUsedTime = ts |
| 260 lastUsedTimeLock.Unlock() |
| 261 return fmt.Sprintf("%v_%v", os.Getpid(), ts) |
| 262 } |
OLD | NEW |