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
|
// 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"
)
// 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
}
// 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
})
}
|