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  }