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 | |
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 } | |
OLD | NEW |