This repository has been archived by the owner on Jan 8, 2024. It is now read-only.
/
file.go
358 lines (325 loc) · 9.12 KB
/
file.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
// Copyright 2015 Keybase, Inc. All rights reserved. Use of
// this source code is governed by the included BSD license.
package util
import (
"fmt"
"io"
"net/url"
"os"
"path/filepath"
"runtime"
"strings"
"time"
)
// File uses a safer file API
type File struct {
name string
data []byte
perm os.FileMode
}
// SafeWriter defines a writer that is safer (atomic)
type SafeWriter interface {
GetFilename() string
WriteTo(io.Writer) (int64, error)
}
// NewFile returns a File
func NewFile(name string, data []byte, perm os.FileMode) File {
return File{name, data, perm}
}
// Save file
func (f File) Save(log Log) error {
return safeWriteToFile(f, f.perm, log)
}
// GetFilename returns the file name for SafeWriter
func (f File) GetFilename() string {
return f.name
}
// WriteTo is for SafeWriter
func (f File) WriteTo(w io.Writer) (int64, error) {
n, err := w.Write(f.data)
return int64(n), err
}
// safeWriteToFile to safely write to a file
func safeWriteToFile(t SafeWriter, mode os.FileMode, log Log) error {
filename := t.GetFilename()
if filename == "" {
return fmt.Errorf("No filename")
}
log.Debugf("Writing to %s", filename)
tempFilename, tempFile, err := openTempFile(filename+"-", "", mode)
log.Debugf("Temporary file generated: %s", tempFilename)
if err != nil {
return err
}
_, err = t.WriteTo(tempFile)
if err != nil {
log.Errorf("Error writing temporary file %s: %s", tempFilename, err)
_ = tempFile.Close()
_ = os.Remove(tempFilename)
return err
}
err = tempFile.Close()
if err != nil {
log.Errorf("Error closing temporary file %s: %s", tempFilename, err)
_ = os.Remove(tempFilename)
return err
}
err = os.Rename(tempFilename, filename)
if err != nil {
log.Errorf("Error renaming temporary file %s to %s: %s", tempFilename, filename, err)
_ = os.Remove(tempFilename)
return err
}
log.Debugf("Wrote to %s", filename)
return nil
}
// Close closes a file and ignores the error.
// This satisfies lint checks when using with defer and you don't care if there
// is an error, so instead of:
//
// defer func() { _ = f.Close() }()
// defer Close(f)
func Close(f io.Closer) {
if f == nil {
return
}
_ = f.Close()
}
// RemoveFileAtPath removes a file at path (and any children) ignoring any error.
// We do nothing if path == "".
// This satisfies lint checks when using with defer and you don't care if there
// is an error, so instead of:
//
// defer func() { _ = os.Remove(path) }()
// defer RemoveFileAtPath(path)
func RemoveFileAtPath(path string) {
if path == "" {
return
}
_ = os.RemoveAll(path)
}
// openTempFile creates an opened temporary file.
//
// openTempFile("foo", ".zip", 0755) => "foo.RCG2KUSCGYOO3PCKNWQHBOXBKACOPIKL.zip"
// openTempFile(path.Join(os.TempDir(), "foo"), "", 0600) => "/tmp/foo.RCG2KUSCGYOO3PCKNWQHBOXBKACOPIKL"
func openTempFile(prefix string, suffix string, mode os.FileMode) (string, *os.File, error) {
filename, err := RandomID(prefix)
if err != nil {
return "", nil, err
}
if suffix != "" {
filename += suffix
}
flags := os.O_WRONLY | os.O_CREATE | os.O_EXCL
if mode == 0 {
mode = 0600
}
file, err := os.OpenFile(filename, flags, mode)
return filename, file, err
}
// FileExists returns whether the given file or directory exists or not
func FileExists(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil {
return true, nil
}
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
// MakeParentDirs ensures parent directory exist for path
func MakeParentDirs(path string, mode os.FileMode, log Log) error {
// 2nd return value here is filename (not an error), which is not needed
dir, _ := filepath.Split(path)
if dir == "" {
return fmt.Errorf("No base directory")
}
return MakeDirs(dir, mode, log)
}
// MakeDirs ensures directory exists for path
func MakeDirs(dir string, mode os.FileMode, log Log) error {
exists, err := FileExists(dir)
if err != nil {
return err
}
if !exists {
log.Debugf("Creating: %s\n", dir)
err = os.MkdirAll(dir, mode)
if err != nil {
return err
}
}
return nil
}
// TempPath returns a temporary unique file path.
// If for some reason we can't obtain random data, we still return a valid
// path, which may not be as unique.
// If tempDir is "", then os.TempDir() is used.
func TempPath(tempDir string, prefix string) string {
if tempDir == "" {
tempDir = os.TempDir()
}
filename, err := RandomID(prefix)
if err != nil {
// We had an error getting random bytes, we'll use current nanoseconds
filename = fmt.Sprintf("%s%d", prefix, time.Now().UnixNano())
}
path := filepath.Join(tempDir, filename)
return path
}
// WriteTempFile creates a unique temp file with data.
//
// For example:
//
// WriteTempFile("Test.", byte[]("test data"), 0600)
func WriteTempFile(prefix string, data []byte, mode os.FileMode) (string, error) {
path := TempPath("", prefix)
if err := os.WriteFile(path, data, mode); err != nil {
return "", err
}
return path, nil
}
// MakeTempDir creates a unique temp directory.
//
// For example:
//
// MakeTempDir("Test.", 0700)
func MakeTempDir(prefix string, mode os.FileMode) (string, error) {
path := TempPath("", prefix)
if err := os.MkdirAll(path, mode); err != nil {
return "", err
}
return path, nil
}
// IsDirReal returns true if directory exists and is a real directory (not a symlink).
// If it returns false, an error will be set explaining why.
func IsDirReal(path string) (bool, error) {
fileInfo, err := os.Lstat(path)
if err != nil {
return false, err
}
// Check if symlink
if fileInfo.Mode()&os.ModeSymlink != 0 {
return false, fmt.Errorf("Path is a symlink")
}
if !fileInfo.Mode().IsDir() {
return false, fmt.Errorf("Path is not a directory")
}
return true, nil
}
// MoveFile moves a file safely.
// It will create parent directories for destinationPath if they don't exist.
// If the destination already exists and you specify a tmpDir, it will move
// it there, otherwise it will be removed.
func MoveFile(sourcePath string, destinationPath string, tmpDir string, log Log) error {
if _, statErr := os.Stat(destinationPath); statErr == nil {
if tmpDir == "" {
log.Infof("Removing existing destination path: %s", destinationPath)
if removeErr := os.RemoveAll(destinationPath); removeErr != nil {
return removeErr
}
} else {
tmpPath := filepath.Join(tmpDir, filepath.Base(destinationPath))
log.Infof("Moving existing destination %q to %q", destinationPath, tmpPath)
if tmpMoveErr := os.Rename(destinationPath, tmpPath); tmpMoveErr != nil {
return tmpMoveErr
}
}
}
if err := MakeParentDirs(destinationPath, 0700, log); err != nil {
return err
}
log.Infof("Moving %s to %s", sourcePath, destinationPath)
// Rename will copy over an existing destination
return os.Rename(sourcePath, destinationPath)
}
// CopyFile copies a file safely.
// It will create parent directories for destinationPath if they don't exist.
// It will overwrite an existing destinationPath.
func CopyFile(sourcePath string, destinationPath string, log Log) error {
log.Infof("Copying %s to %s", sourcePath, destinationPath)
in, err := os.Open(sourcePath)
if err != nil {
return err
}
defer Close(in)
if _, statErr := os.Stat(destinationPath); statErr == nil {
log.Infof("Removing existing destination path: %s", destinationPath)
if removeErr := os.RemoveAll(destinationPath); removeErr != nil {
return removeErr
}
}
if makeDirErr := MakeParentDirs(destinationPath, 0700, log); makeDirErr != nil {
return makeDirErr
}
out, err := os.Create(destinationPath)
if err != nil {
return err
}
defer Close(out)
_, err = io.Copy(out, in)
closeErr := out.Close()
if err != nil {
return err
}
return closeErr
}
// ReadFile returns data for file at path
func ReadFile(path string) ([]byte, error) {
file, err := os.Open(path)
if err != nil {
return nil, err
}
defer Close(file)
data, err := io.ReadAll(file)
if err != nil {
return nil, err
}
return data, nil
}
func convertPathForWindows(path string) string {
return "/" + strings.ReplaceAll(path, `\`, `/`)
}
// URLStringForPath returns an URL as string with file scheme for path.
// For example,
//
// /usr/local/go/bin => file:///usr/local/go/bin
// C:\Go\bin => file:///C:/Go/bin
func URLStringForPath(path string) string {
if runtime.GOOS == "windows" {
path = convertPathForWindows(path)
}
u := &url.URL{Path: path}
encodedPath := u.String()
return fmt.Sprintf("%s://%s", fileScheme, encodedPath)
}
// PathFromURL returns path for file URL scheme
// For example,
//
// file:///usr/local/go/bin => /usr/local/go/bin
// file:///C:/Go/bin => C:\Go\bin
func PathFromURL(u *url.URL) string {
path := u.Path
if runtime.GOOS == "windows" && u.Scheme == fileScheme {
// Remove leading slash for Windows
path = strings.TrimPrefix(path, "/")
path = filepath.FromSlash(path)
}
return path
}
// Touch a file, updating its modification time
func Touch(path string) error {
f, err := os.OpenFile(path, os.O_RDONLY|os.O_CREATE|os.O_TRUNC, 0600)
Close(f)
return err
}
// FileModTime returns modification time for file.
// If file doesn't exist returns error.
func FileModTime(path string) (time.Time, error) {
info, err := os.Stat(path)
if err != nil {
return time.Time{}, err
}
return info.ModTime(), nil
}