]>
Commit | Line | Data |
---|---|---|
bae9f6d2 JC |
1 | package homedir |
2 | ||
3 | import ( | |
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. | |
17 | var DisableCache bool | |
18 | ||
19 | var homedirCache string | |
20 | var 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. | |
26 | func 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. | |
58 | func 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 | func dirUnix() (string, error) { | |
80 | // First prefer the HOME environmental variable | |
81 | if home := os.Getenv("HOME"); home != "" { | |
82 | return home, nil | |
83 | } | |
84 | ||
85 | // If that fails, try getent | |
86 | var stdout bytes.Buffer | |
87 | cmd := exec.Command("getent", "passwd", strconv.Itoa(os.Getuid())) | |
88 | cmd.Stdout = &stdout | |
89 | if err := cmd.Run(); err != nil { | |
90 | // If the error is ErrNotFound, we ignore it. Otherwise, return it. | |
91 | if err != exec.ErrNotFound { | |
92 | return "", err | |
93 | } | |
94 | } else { | |
95 | if passwd := strings.TrimSpace(stdout.String()); passwd != "" { | |
96 | // username:password:uid:gid:gecos:home:shell | |
97 | passwdParts := strings.SplitN(passwd, ":", 7) | |
98 | if len(passwdParts) > 5 { | |
99 | return passwdParts[5], nil | |
100 | } | |
101 | } | |
102 | } | |
103 | ||
104 | // If all else fails, try the shell | |
105 | stdout.Reset() | |
106 | cmd = exec.Command("sh", "-c", "cd && pwd") | |
107 | cmd.Stdout = &stdout | |
108 | if err := cmd.Run(); err != nil { | |
109 | return "", err | |
110 | } | |
111 | ||
112 | result := strings.TrimSpace(stdout.String()) | |
113 | if result == "" { | |
114 | return "", errors.New("blank output when reading home directory") | |
115 | } | |
116 | ||
117 | return result, nil | |
118 | } | |
119 | ||
120 | func dirWindows() (string, error) { | |
121 | // First prefer the HOME environmental variable | |
122 | if home := os.Getenv("HOME"); home != "" { | |
123 | return home, nil | |
124 | } | |
125 | ||
126 | drive := os.Getenv("HOMEDRIVE") | |
127 | path := os.Getenv("HOMEPATH") | |
128 | home := drive + path | |
129 | if drive == "" || path == "" { | |
130 | home = os.Getenv("USERPROFILE") | |
131 | } | |
132 | if home == "" { | |
133 | return "", errors.New("HOMEDRIVE, HOMEPATH, and USERPROFILE are blank") | |
134 | } | |
135 | ||
136 | return home, nil | |
137 | } |