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

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: extract-cipd-fs 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
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
nodir 2015/06/03 19:30:27 nit: high-level
Vadim Sh. 2015/06/03 22:35:08 Done.
19 // system partition. All functions operate in terms of native file paths. It
20 // exists mostly to hide difference 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 err = os.MkdirAll(path, 0777)
116 if err == nil {
nodir 2015/06/03 19:30:27 nit: join lines 115-116?
Vadim Sh. 2015/06/03 22:35:08 Done.
117 return path, nil
118 }
119 return "", err
120 }
121
122 func (f *fsImpl) EnsureSymlink(path string, target string) error {
123 path, err := f.ToAbsPath(path)
124 if err != nil {
125 return err
126 }
127 if existing, _ := os.Readlink(path); existing == target {
128 return nil
129 }
130 if _, err := f.EnsureDirectory(filepath.Dir(path)); err != nil {
131 return err
132 }
133
134 // Create a new symlink file, can't modify existing one in place.
135 temp := fmt.Sprintf("%s_%s", path, pseudoRand())
136 if err := os.Symlink(target, temp); err != nil {
137 return err
138 }
139
140 // 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.
141 err = os.Rename(temp, path)
142 if err != nil {
143 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
144 return err
145 }
146
147 return nil
148 }
149
150 func (f *fsImpl) EnsureFileGone(path string) error {
151 path, err := f.ToAbsPath(path)
152 if err != nil {
153 return err
154 }
155 err = os.Remove(path)
156 if err != nil && !os.IsNotExist(err) {
157 f.logger.Warningf("fs: failed to remove %s - %s", path, err)
158 return err
159 }
160 return nil
161 }
162
163 func (f *fsImpl) EnsureDirectoryGone(path string) error {
164 path, err := f.ToAbsPath(path)
165 if err != nil {
166 return err
167 }
168 // Make directory "disappear" instantly by renaming it first.
169 temp := fmt.Sprintf("%s_%v", path, pseudoRand())
170 if err = os.Rename(path, temp); err != nil {
171 if os.IsNotExist(err) {
172 return nil
173 }
174 f.logger.Warningf("fs: failed to rename directory %s - %s", path , err)
175 return err
176 }
177 if err = os.RemoveAll(temp); err != nil {
178 f.logger.Warningf("fs: failed to remove directory %s - %s", temp , err)
179 return err
180 }
181 return nil
182 }
183
184 func (f *fsImpl) Replace(oldpath, newpath string) error {
185 oldpath, err := f.ToAbsPath(oldpath)
186 if err != nil {
187 return err
188 }
189 newpath, err = f.ToAbsPath(newpath)
190 if err != nil {
191 return err
192 }
193 if oldpath == newpath {
194 return nil
195 }
196
197 // Make sure oldpath exists before doing heavy stuff.
198 if _, err = os.Stat(oldpath); err != nil {
199 return err
200 }
201
202 // Make parent directory of newpath.
203 if _, err = f.EnsureDirectory(filepath.Dir(newpath)); err != nil {
204 return err
205 }
206
207 // Try a regular move first. Replaces files and empty directories.
208 if err = os.Rename(oldpath, newpath); err == nil {
209 return nil
210 }
211
212 // Move existing path away, if it is there.
213 temp := fmt.Sprintf("%s_%s", newpath, pseudoRand())
214 if err = os.Rename(newpath, temp); err != nil {
215 if !os.IsNotExist(err) {
216 f.logger.Warningf("fs: failed to rename(%v, %v) - %s", n ewpath, temp, err)
217 return err
218 }
219 temp = ""
220 }
221
222 // 'newpath' now should be available.
223 if err := os.Rename(oldpath, newpath); err != nil {
224 f.logger.Warningf("fs: failed to rename(%v, %v) - %s", oldpath, newpath, err)
225 // Try to return the path back... May be too late already.
226 if temp != "" {
227 if err := os.Rename(temp, newpath); err != nil {
228 f.logger.Errorf("fs: failed to rename(%v, %v) af ter unsuccessful move - %s", temp, newpath, err)
229 }
230 }
231 return err
232 }
233
234 // Cleanup the garbage left. Not a error if fails.
235 if temp != "" {
236 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.
237 f.logger.Warningf("fs: failed to cleanup garbage after f ile replace - %s", err)
238 }
239 }
240 return nil
241 }
242
243 /// Internal stuff.
244
245 var lastUsedTime int64
246 var lastUsedTimeLock sync.Mutex
247
248 // pseudoRand returns "random enough" string that can be used in file system
249 // paths of temp files.
250 func pseudoRand() string {
251 lastUsedTimeLock.Lock()
252 ts := time.Now().UnixNano()
253 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.
254 ts++
255 }
256 lastUsedTime = ts
257 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
258 return fmt.Sprintf("%v_%v", os.Getpid(), ts)
259 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698