David K. Bainbridge | 528b318 | 2017-01-23 08:51:59 -0800 | [diff] [blame^] | 1 | // Copyright 2013 Canonical Ltd. |
| 2 | // Licensed under the LGPLv3, see LICENCE file for details. |
| 3 | |
| 4 | // +build windows |
| 5 | |
| 6 | package utils |
| 7 | |
| 8 | import ( |
| 9 | "fmt" |
| 10 | "os" |
| 11 | "path/filepath" |
| 12 | "syscall" |
| 13 | "unsafe" |
| 14 | |
| 15 | "github.com/juju/errors" |
| 16 | ) |
| 17 | |
| 18 | const ( |
| 19 | movefile_replace_existing = 0x1 |
| 20 | movefile_write_through = 0x8 |
| 21 | ) |
| 22 | |
| 23 | //sys moveFileEx(lpExistingFileName *uint16, lpNewFileName *uint16, dwFlags uint32) (err error) = MoveFileExW |
| 24 | |
| 25 | // MoveFile atomically moves the source file to the destination, returning |
| 26 | // whether the file was moved successfully. If the destination already exists, |
| 27 | // it returns an error rather than overwrite it. |
| 28 | func MoveFile(source, destination string) (bool, error) { |
| 29 | src, err := syscall.UTF16PtrFromString(source) |
| 30 | if err != nil { |
| 31 | return false, &os.LinkError{"move", source, destination, err} |
| 32 | } |
| 33 | dest, err := syscall.UTF16PtrFromString(destination) |
| 34 | if err != nil { |
| 35 | return false, &os.LinkError{"move", source, destination, err} |
| 36 | } |
| 37 | |
| 38 | // see http://msdn.microsoft.com/en-us/library/windows/desktop/aa365240(v=vs.85).aspx |
| 39 | if err := moveFileEx(src, dest, movefile_write_through); err != nil { |
| 40 | return false, &os.LinkError{"move", source, destination, err} |
| 41 | } |
| 42 | return true, nil |
| 43 | |
| 44 | } |
| 45 | |
| 46 | // ReplaceFile atomically replaces the destination file or directory with the source. |
| 47 | // The errors that are returned are identical to those returned by os.Rename. |
| 48 | func ReplaceFile(source, destination string) error { |
| 49 | src, err := syscall.UTF16PtrFromString(source) |
| 50 | if err != nil { |
| 51 | return &os.LinkError{"replace", source, destination, err} |
| 52 | } |
| 53 | dest, err := syscall.UTF16PtrFromString(destination) |
| 54 | if err != nil { |
| 55 | return &os.LinkError{"replace", source, destination, err} |
| 56 | } |
| 57 | |
| 58 | // see http://msdn.microsoft.com/en-us/library/windows/desktop/aa365240(v=vs.85).aspx |
| 59 | if err := moveFileEx(src, dest, movefile_replace_existing|movefile_write_through); err != nil { |
| 60 | return &os.LinkError{"replace", source, destination, err} |
| 61 | } |
| 62 | return nil |
| 63 | } |
| 64 | |
| 65 | // MakeFileURL returns a proper file URL for the given path/directory |
| 66 | func MakeFileURL(in string) string { |
| 67 | in = filepath.ToSlash(in) |
| 68 | // for windows at least should be <letter>: to be considered valid |
| 69 | // so we cant do anything with less than that. |
| 70 | if len(in) < 2 { |
| 71 | return in |
| 72 | } |
| 73 | if string(in[1]) != ":" { |
| 74 | return in |
| 75 | } |
| 76 | // since go 1.6 http client will only take this format. |
| 77 | return "file://" + in |
| 78 | } |
| 79 | |
| 80 | func getUserSID(username string) (string, error) { |
| 81 | sid, _, _, e := syscall.LookupSID("", username) |
| 82 | if e != nil { |
| 83 | return "", e |
| 84 | } |
| 85 | sidStr, err := sid.String() |
| 86 | return sidStr, err |
| 87 | } |
| 88 | |
| 89 | func readRegString(h syscall.Handle, key string) (value string, err error) { |
| 90 | var typ uint32 |
| 91 | var buf uint32 |
| 92 | |
| 93 | // Get size of registry key |
| 94 | err = syscall.RegQueryValueEx(h, syscall.StringToUTF16Ptr(key), nil, &typ, nil, &buf) |
| 95 | if err != nil { |
| 96 | return value, err |
| 97 | } |
| 98 | |
| 99 | n := make([]uint16, buf/2+1) |
| 100 | err = syscall.RegQueryValueEx(h, syscall.StringToUTF16Ptr(key), nil, &typ, (*byte)(unsafe.Pointer(&n[0])), &buf) |
| 101 | if err != nil { |
| 102 | return value, err |
| 103 | } |
| 104 | return syscall.UTF16ToString(n[:]), err |
| 105 | } |
| 106 | |
| 107 | func homeFromRegistry(sid string) (string, error) { |
| 108 | var h syscall.Handle |
| 109 | // This key will exist on all platforms we support the agent on (windows server 2008 and above) |
| 110 | keyPath := fmt.Sprintf("Software\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList\\%s", sid) |
| 111 | err := syscall.RegOpenKeyEx(syscall.HKEY_LOCAL_MACHINE, |
| 112 | syscall.StringToUTF16Ptr(keyPath), |
| 113 | 0, syscall.KEY_READ, &h) |
| 114 | if err != nil { |
| 115 | return "", err |
| 116 | } |
| 117 | defer syscall.RegCloseKey(h) |
| 118 | str, err := readRegString(h, "ProfileImagePath") |
| 119 | if err != nil { |
| 120 | return "", err |
| 121 | } |
| 122 | return str, nil |
| 123 | } |
| 124 | |
| 125 | // homeDir returns a local user home dir on Windows |
| 126 | // user.Lookup() does not populate Gid and HomeDir on Windows, |
| 127 | // so we get it from the registry |
| 128 | func homeDir(user string) (string, error) { |
| 129 | u, err := getUserSID(user) |
| 130 | if err != nil { |
| 131 | return "", errors.NewUserNotFound(err, "no such user") |
| 132 | } |
| 133 | return homeFromRegistry(u) |
| 134 | } |
| 135 | |
| 136 | // ChownPath is not implemented for Windows. |
| 137 | func ChownPath(path, username string) error { |
| 138 | // This only exists to allow building on Windows. User lookup and |
| 139 | // file ownership needs to be handled in a completely different |
| 140 | // way and hasn't yet been implemented. |
| 141 | return nil |
| 142 | } |