1package homedir 2 3import ( 4 "bytes" 5 "errors" 6 "os" 7 "os/exec" 8 "path/filepath" 9 "runtime" 10 "strconv" 11 "strings" 12 "sync" 13) 14 15// DisableCache will disable caching of the home directory. Caching is enabled 16// by default. 17var DisableCache bool 18 19var homedirCache string 20var cacheLock sync.RWMutex 21 22// Dir returns the home directory for the executing user. 23// 24// This uses an OS-specific method for discovering the home directory. 25// An error is returned if a home directory cannot be detected. 26func Dir() (string, error) { 27 if !DisableCache { 28 cacheLock.RLock() 29 cached := homedirCache 30 cacheLock.RUnlock() 31 if cached != "" { 32 return cached, nil 33 } 34 } 35 36 cacheLock.Lock() 37 defer cacheLock.Unlock() 38 39 var result string 40 var err error 41 if runtime.GOOS == "windows" { 42 result, err = dirWindows() 43 } else { 44 // Unix-like system, so just assume Unix 45 result, err = dirUnix() 46 } 47 48 if err != nil { 49 return "", err 50 } 51 homedirCache = result 52 return result, nil 53} 54 55// Expand expands the path to include the home directory if the path 56// is prefixed with `~`. If it isn't prefixed with `~`, the path is 57// returned as-is. 58func Expand(path string) (string, error) { 59 if len(path) == 0 { 60 return path, nil 61 } 62 63 if path[0] != '~' { 64 return path, nil 65 } 66 67 if len(path) > 1 && path[1] != '/' && path[1] != '\\' { 68 return "", errors.New("cannot expand user-specific home dir") 69 } 70 71 dir, err := Dir() 72 if err != nil { 73 return "", err 74 } 75 76 return filepath.Join(dir, path[1:]), nil 77} 78 79// Reset clears the cache, forcing the next call to Dir to re-detect 80// the home directory. This generally never has to be called, but can be 81// useful in tests if you're modifying the home directory via the HOME 82// env var or something. 83func Reset() { 84 cacheLock.Lock() 85 defer cacheLock.Unlock() 86 homedirCache = "" 87} 88 89func dirUnix() (string, error) { 90 homeEnv := "HOME" 91 if runtime.GOOS == "plan9" { 92 // On plan9, env vars are lowercase. 93 homeEnv = "home" 94 } 95 96 // First prefer the HOME environmental variable 97 if home := os.Getenv(homeEnv); home != "" { 98 return home, nil 99 } 100 101 var stdout bytes.Buffer 102 103 // If that fails, try OS specific commands 104 if runtime.GOOS == "darwin" { 105 cmd := exec.Command("sh", "-c", `dscl -q . -read /Users/"$(whoami)" NFSHomeDirectory | sed 's/^[^ ]*: //'`) 106 cmd.Stdout = &stdout 107 if err := cmd.Run(); err == nil { 108 result := strings.TrimSpace(stdout.String()) 109 if result != "" { 110 return result, nil 111 } 112 } 113 } else { 114 cmd := exec.Command("getent", "passwd", strconv.Itoa(os.Getuid())) 115 cmd.Stdout = &stdout 116 if err := cmd.Run(); err != nil { 117 // If the error is ErrNotFound, we ignore it. Otherwise, return it. 118 if err != exec.ErrNotFound { 119 return "", err 120 } 121 } else { 122 if passwd := strings.TrimSpace(stdout.String()); passwd != "" { 123 // username:password:uid:gid:gecos:home:shell 124 passwdParts := strings.SplitN(passwd, ":", 7) 125 if len(passwdParts) > 5 { 126 return passwdParts[5], nil 127 } 128 } 129 } 130 } 131 132 // If all else fails, try the shell 133 stdout.Reset() 134 cmd := exec.Command("sh", "-c", "cd && pwd") 135 cmd.Stdout = &stdout 136 if err := cmd.Run(); err != nil { 137 return "", err 138 } 139 140 result := strings.TrimSpace(stdout.String()) 141 if result == "" { 142 return "", errors.New("blank output when reading home directory") 143 } 144 145 return result, nil 146} 147 148func dirWindows() (string, error) { 149 // First prefer the HOME environmental variable 150 if home := os.Getenv("HOME"); home != "" { 151 return home, nil 152 } 153 154 // Prefer standard environment variable USERPROFILE 155 if home := os.Getenv("USERPROFILE"); home != "" { 156 return home, nil 157 } 158 159 drive := os.Getenv("HOMEDRIVE") 160 path := os.Getenv("HOMEPATH") 161 home := drive + path 162 if drive == "" || path == "" { 163 return "", errors.New("HOMEDRIVE, HOMEPATH, or USERPROFILE are blank") 164 } 165 166 return home, nil 167} 168