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

Side by Side 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: Created 5 years, 6 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 unified diff | Download patch
« no previous file with comments | « go/src/infra/tools/cipd/local/files_test.go ('k') | go/src/infra/tools/cipd/local/fs_test.go » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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 }
OLDNEW
« no previous file with comments | « go/src/infra/tools/cipd/local/files_test.go ('k') | go/src/infra/tools/cipd/local/fs_test.go » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698