github.com/cloudbase/juju-core@v0.0.0-20140504232958-a7271ac7912f/utils/file.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package utils 5 6 import ( 7 "fmt" 8 "io" 9 "io/ioutil" 10 "os" 11 "os/user" 12 "path" 13 "path/filepath" 14 "regexp" 15 "runtime" 16 17 "launchpad.net/juju-core/juju/osenv" 18 ) 19 20 // UserHomeDir returns the home directory for the specified user, or the 21 // home directory for the current user if the specified user is empty. 22 func UserHomeDir(userName string) (homeDir string, err error) { 23 var u *user.User 24 if userName == "" { 25 // TODO (wallyworld) - fix tests on Windows 26 // Ordinarily, we'd always use user.Current() to get the current user 27 // and then get the HomeDir from that. But our tests rely on poking 28 // a value into $HOME in order to override the normal home dir for the 29 // current user. So on *nix, we're forced to use osenv.Home() to make 30 // the tests pass. All of our tests currently construct paths with the 31 // default user in mind eg "~/foo". 32 if runtime.GOOS == "windows" { 33 u, err = user.Current() 34 } else { 35 return osenv.Home(), nil 36 } 37 } else { 38 u, err = user.Lookup(userName) 39 if err != nil { 40 return "", err 41 } 42 } 43 return u.HomeDir, nil 44 } 45 46 var userHomePathRegexp = regexp.MustCompile("(~(?P<user>[^/]*))(?P<path>.*)") 47 48 // NormalizePath expands a path containing ~ to its absolute form, 49 // and removes any .. or . path elements. 50 func NormalizePath(dir string) (string, error) { 51 if userHomePathRegexp.MatchString(dir) { 52 user := userHomePathRegexp.ReplaceAllString(dir, "$user") 53 userHomeDir, err := UserHomeDir(user) 54 if err != nil { 55 return "", err 56 } 57 dir = userHomePathRegexp.ReplaceAllString(dir, fmt.Sprintf("%s$path", userHomeDir)) 58 } 59 return filepath.Clean(dir), nil 60 } 61 62 // JoinServerPath joins any number of path elements into a single path, adding 63 // a path separator (based on the current juju server OS) if necessary. The 64 // result is Cleaned; in particular, all empty strings are ignored. 65 func JoinServerPath(elem ...string) string { 66 return path.Join(elem...) 67 } 68 69 // UniqueDirectory returns "path/name" if that directory doesn't exist. If it 70 // does, the method starts appending .1, .2, etc until a unique name is found. 71 func UniqueDirectory(path, name string) (string, error) { 72 dir := filepath.Join(path, name) 73 _, err := os.Stat(dir) 74 if os.IsNotExist(err) { 75 return dir, nil 76 } 77 for i := 1; ; i++ { 78 dir := filepath.Join(path, fmt.Sprintf("%s.%d", name, i)) 79 _, err := os.Stat(dir) 80 if os.IsNotExist(err) { 81 return dir, nil 82 } else if err != nil { 83 return "", err 84 } 85 } 86 } 87 88 // CopyFile writes the contents of the given source file to dest. 89 func CopyFile(dest, source string) error { 90 df, err := os.Create(dest) 91 if err != nil { 92 return err 93 } 94 f, err := os.Open(source) 95 if err != nil { 96 return err 97 } 98 defer f.Close() 99 _, err = io.Copy(df, f) 100 return err 101 } 102 103 func WriteFile(filename string, contents []byte, perms os.FileMode) (err error) { 104 return ioutil.WriteFile(filename, contents, perms) 105 } 106 107 // AtomicWriteFileAndChange atomically writes the filename with the 108 // given contents and calls the given function after the contents were 109 // written, but before the file is renamed. 110 func AtomicWriteFileAndChange(filename string, contents []byte, change func(*os.File) error) (err error) { 111 dir, file := filepath.Split(filename) 112 f, err := ioutil.TempFile(dir, file) 113 if err != nil { 114 return fmt.Errorf("cannot create temp file: %v", err) 115 } 116 defer f.Close() 117 defer func() { 118 if err != nil { 119 // Don't leave the temp file lying around on error. 120 os.Remove(f.Name()) 121 } 122 }() 123 if _, err := f.Write(contents); err != nil { 124 return fmt.Errorf("cannot write %q contents: %v", filename, err) 125 } 126 if err := change(f); err != nil { 127 return err 128 } 129 if err := ReplaceFile(f.Name(), filename); err != nil { 130 return fmt.Errorf("cannot replace %q with %q: %v", f.Name(), filename, err) 131 } 132 return nil 133 } 134 135 // AtomicWriteFile atomically writes the filename with the given 136 // contents and permissions, replacing any existing file at the same 137 // path. 138 func AtomicWriteFile(filename string, contents []byte, perms os.FileMode) (err error) { 139 return AtomicWriteFileAndChange(filename, contents, func(f *os.File) error { 140 if err := f.Chmod(perms); err != nil { 141 return fmt.Errorf("cannot set permissions: %v", err) 142 } 143 return nil 144 }) 145 }