-
Notifications
You must be signed in to change notification settings - Fork 89
/
file.go
165 lines (152 loc) · 4.92 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
// Copyright 2013 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package utils
import (
"fmt"
"io"
"io/ioutil"
"os"
"path"
"path/filepath"
"regexp"
"github.com/juju/errors"
)
// UserHomeDir returns the home directory for the specified user, or the
// home directory for the current user if the specified user is empty.
func UserHomeDir(userName string) (hDir string, err error) {
if userName == "" {
// TODO (wallyworld) - fix tests on Windows
// Ordinarily, we'd always use user.Current() to get the current user
// and then get the HomeDir from that. But our tests rely on poking
// a value into $HOME in order to override the normal home dir for the
// current user. So we're forced to use Home() to make the tests pass.
// All of our tests currently construct paths with the default user in
// mind eg "~/foo".
return Home(), nil
}
hDir, err = homeDir(userName)
if err != nil {
return "", err
}
return hDir, nil
}
// Only match paths starting with ~ (~user/test, ~/test). This will prevent
// accidental expansion on Windows when short form paths are present (C:\users\ADMINI~1\test)
var userHomePathRegexp = regexp.MustCompile("(^~(?P<user>[^/]*))(?P<path>.*)")
// NormalizePath expands a path containing ~ to its absolute form,
// and removes any .. or . path elements.
func NormalizePath(dir string) (string, error) {
if userHomePathRegexp.MatchString(dir) {
user := userHomePathRegexp.ReplaceAllString(dir, "$user")
userHomeDir, err := UserHomeDir(user)
if err != nil {
return "", err
}
dir = userHomePathRegexp.ReplaceAllString(dir, fmt.Sprintf("%s$path", userHomeDir))
}
return filepath.Clean(dir), nil
}
// ExpandPath normalises (via Normalize) a path returning an absolute path.
func ExpandPath(path string) (string, error) {
normPath, err := NormalizePath(path)
if err != nil {
return "", errors.Annotate(err, "unable to normalise file path")
}
return filepath.Abs(normPath)
}
// EnsureBaseDir ensures that path is always prefixed by baseDir,
// allowing for the fact that path might have a Window drive letter in
// it.
func EnsureBaseDir(baseDir, path string) string {
if baseDir == "" {
return path
}
volume := filepath.VolumeName(path)
return filepath.Join(baseDir, path[len(volume):])
}
// JoinServerPath joins any number of path elements into a single path, adding
// a path separator (based on the current juju server OS) if necessary. The
// result is Cleaned; in particular, all empty strings are ignored.
func JoinServerPath(elem ...string) string {
return path.Join(elem...)
}
// UniqueDirectory returns "path/name" if that directory doesn't exist. If it
// does, the method starts appending .1, .2, etc until a unique name is found.
func UniqueDirectory(path, name string) (string, error) {
dir := filepath.Join(path, name)
_, err := os.Stat(dir)
if os.IsNotExist(err) {
return dir, nil
}
for i := 1; ; i++ {
dir := filepath.Join(path, fmt.Sprintf("%s.%d", name, i))
_, err := os.Stat(dir)
if os.IsNotExist(err) {
return dir, nil
} else if err != nil {
return "", err
}
}
}
// CopyFile writes the contents of the given source file to dest.
func CopyFile(dest, source string) error {
df, err := os.Create(dest)
if err != nil {
return err
}
f, err := os.Open(source)
if err != nil {
return err
}
defer f.Close()
_, err = io.Copy(df, f)
return err
}
// AtomicWriteFileAndChange atomically writes the filename with the
// given contents and calls the given function after the contents were
// written, but before the file is renamed.
func AtomicWriteFileAndChange(filename string, contents []byte, change func(string) error) (err error) {
dir, file := filepath.Split(filename)
f, err := ioutil.TempFile(dir, file)
if err != nil {
return fmt.Errorf("cannot create temp file: %v", err)
}
defer f.Close()
defer func() {
if err != nil {
// Don't leave the temp file lying around on error.
// Close the file before removing. Trying to remove an open file on
// Windows will fail.
f.Close()
os.Remove(f.Name())
}
}()
if _, err := f.Write(contents); err != nil {
return fmt.Errorf("cannot write %q contents: %v", filename, err)
}
if err := f.Sync(); err != nil {
return err
}
if err := f.Close(); err != nil {
return err
}
if err := change(f.Name()); err != nil {
return err
}
if err := ReplaceFile(f.Name(), filename); err != nil {
return fmt.Errorf("cannot replace %q with %q: %v", f.Name(), filename, err)
}
return nil
}
// AtomicWriteFile atomically writes the filename with the given
// contents and permissions, replacing any existing file at the same
// path.
func AtomicWriteFile(filename string, contents []byte, perms os.FileMode) (err error) {
return AtomicWriteFileAndChange(filename, contents, func(f string) error {
// FileMod.Chmod() is not implemented on Windows, however, os.Chmod() is
if err := os.Chmod(f, perms); err != nil {
return fmt.Errorf("cannot set permissions: %v", err)
}
return nil
})
}