]>
Commit | Line | Data |
---|---|---|
997579be | 1 | /* |
359fc2d2 | 2 | * Copyright (C) the libgit2 contributors. All rights reserved. |
997579be SS |
3 | * |
4 | * This file is part of libgit2, distributed under the GNU GPL v2 with | |
5 | * a Linking Exception. For full terms see the included COPYING file. | |
6 | */ | |
7 | ||
eae0bfdc PP |
8 | #include "findfile.h" |
9 | ||
cceae9a2 | 10 | #include "path_w32.h" |
997579be | 11 | #include "utf-conv.h" |
e579e0f7 | 12 | #include "fs_path.h" |
997579be | 13 | |
e579e0f7 MB |
14 | #define REG_GITFORWINDOWS_KEY L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Git_is1" |
15 | #define REG_GITFORWINDOWS_KEY_WOW64 L"SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Git_is1" | |
5540d947 | 16 | |
e579e0f7 | 17 | static int git_win32__expand_path(git_win32_path dest, const wchar_t *src) |
5540d947 | 18 | { |
e579e0f7 | 19 | DWORD len = ExpandEnvironmentStringsW(src, dest, GIT_WIN_PATH_UTF16); |
5540d947 | 20 | |
e579e0f7 | 21 | if (!len || len > GIT_WIN_PATH_UTF16) |
c2c81615 | 22 | return -1; |
5540d947 | 23 | |
c2c81615 | 24 | return 0; |
5540d947 RB |
25 | } |
26 | ||
e579e0f7 | 27 | static int win32_path_to_8(git_str *dest, const wchar_t *src) |
997579be | 28 | { |
c2c81615 | 29 | git_win32_utf8_path utf8_path; |
997579be | 30 | |
c2c81615 | 31 | if (git_win32_path_to_utf8(utf8_path, src) < 0) { |
ac3d33df | 32 | git_error_set(GIT_ERROR_OS, "unable to convert path to UTF-8"); |
c2c81615 | 33 | return -1; |
997579be SS |
34 | } |
35 | ||
c2c81615 | 36 | /* Convert backslashes to forward slashes */ |
e579e0f7 | 37 | git_fs_path_mkposix(utf8_path); |
5540d947 | 38 | |
e579e0f7 | 39 | return git_str_sets(dest, utf8_path); |
997579be SS |
40 | } |
41 | ||
e579e0f7 MB |
42 | static git_win32_path mock_registry; |
43 | static bool mock_registry_set; | |
997579be | 44 | |
e579e0f7 MB |
45 | extern int git_win32__set_registry_system_dir(const wchar_t *mock_sysdir) |
46 | { | |
47 | if (!mock_sysdir) { | |
48 | mock_registry[0] = L'\0'; | |
49 | mock_registry_set = false; | |
50 | } else { | |
51 | size_t len = wcslen(mock_sysdir); | |
52 | ||
53 | if (len > GIT_WIN_PATH_MAX) { | |
54 | git_error_set(GIT_ERROR_INVALID, "mock path too long"); | |
55 | return -1; | |
56 | } | |
997579be | 57 | |
e579e0f7 MB |
58 | wcscpy(mock_registry, mock_sysdir); |
59 | mock_registry_set = true; | |
60 | } | |
997579be | 61 | |
e579e0f7 | 62 | return 0; |
997579be SS |
63 | } |
64 | ||
e579e0f7 MB |
65 | static int lookup_registry_key( |
66 | git_win32_path out, | |
67 | const HKEY hive, | |
68 | const wchar_t* key, | |
69 | const wchar_t *value) | |
997579be | 70 | { |
e579e0f7 MB |
71 | HKEY hkey; |
72 | DWORD type, size; | |
73 | int error = GIT_ENOTFOUND; | |
997579be | 74 | |
e579e0f7 MB |
75 | /* |
76 | * Registry data may not be NUL terminated, provide room to do | |
77 | * it ourselves. | |
78 | */ | |
79 | size = (DWORD)((sizeof(git_win32_path) - 1) * sizeof(wchar_t)); | |
80 | ||
81 | if (RegOpenKeyExW(hive, key, 0, KEY_READ, &hkey) != 0) | |
82 | return GIT_ENOTFOUND; | |
83 | ||
84 | if (RegQueryValueExW(hkey, value, NULL, &type, (LPBYTE)out, &size) == 0 && | |
85 | type == REG_SZ && | |
86 | size > 0 && | |
87 | size < sizeof(git_win32_path)) { | |
88 | size_t wsize = size / sizeof(wchar_t); | |
89 | size_t len = wsize - 1; | |
90 | ||
91 | if (out[wsize - 1] != L'\0') { | |
92 | len = wsize; | |
93 | out[wsize] = L'\0'; | |
94 | } | |
997579be | 95 | |
e579e0f7 MB |
96 | if (out[len - 1] == L'\\') |
97 | out[len - 1] = L'\0'; | |
5540d947 | 98 | |
e579e0f7 MB |
99 | if (_waccess(out, F_OK) == 0) |
100 | error = 0; | |
101 | } | |
997579be | 102 | |
e579e0f7 MB |
103 | RegCloseKey(hkey); |
104 | return error; | |
105 | } | |
997579be | 106 | |
e579e0f7 MB |
107 | static int find_sysdir_in_registry(git_win32_path out) |
108 | { | |
109 | if (mock_registry_set) { | |
110 | if (mock_registry[0] == L'\0') | |
111 | return GIT_ENOTFOUND; | |
997579be | 112 | |
e579e0f7 MB |
113 | wcscpy(out, mock_registry); |
114 | return 0; | |
997579be | 115 | } |
5540d947 | 116 | |
e579e0f7 MB |
117 | if (lookup_registry_key(out, HKEY_CURRENT_USER, REG_GITFORWINDOWS_KEY, L"InstallLocation") == 0 || |
118 | lookup_registry_key(out, HKEY_CURRENT_USER, REG_GITFORWINDOWS_KEY_WOW64, L"InstallLocation") == 0 || | |
119 | lookup_registry_key(out, HKEY_LOCAL_MACHINE, REG_GITFORWINDOWS_KEY, L"InstallLocation") == 0 || | |
120 | lookup_registry_key(out, HKEY_LOCAL_MACHINE, REG_GITFORWINDOWS_KEY_WOW64, L"InstallLocation") == 0) | |
121 | return 0; | |
122 | ||
123 | return GIT_ENOTFOUND; | |
997579be SS |
124 | } |
125 | ||
e579e0f7 | 126 | static int find_sysdir_in_path(git_win32_path out) |
997579be | 127 | { |
e579e0f7 | 128 | size_t out_len; |
5540d947 | 129 | |
e579e0f7 MB |
130 | if (git_win32_path_find_executable(out, L"git.exe") < 0 && |
131 | git_win32_path_find_executable(out, L"git.cmd") < 0) | |
132 | return GIT_ENOTFOUND; | |
997579be | 133 | |
e579e0f7 | 134 | out_len = wcslen(out); |
5540d947 | 135 | |
e579e0f7 MB |
136 | /* Trim the file name */ |
137 | if (out_len <= CONST_STRLEN(L"git.exe")) | |
138 | return GIT_ENOTFOUND; | |
5540d947 | 139 | |
e579e0f7 | 140 | out_len -= CONST_STRLEN(L"git.exe"); |
5540d947 | 141 | |
e579e0f7 MB |
142 | if (out_len && out[out_len - 1] == L'\\') |
143 | out_len--; | |
5540d947 | 144 | |
e579e0f7 MB |
145 | /* |
146 | * Git for Windows usually places the command in a 'bin' or | |
147 | * 'cmd' directory, trim that. | |
148 | */ | |
149 | if (out_len >= CONST_STRLEN(L"\\bin") && | |
150 | wcsncmp(&out[out_len - CONST_STRLEN(L"\\bin")], L"\\bin", CONST_STRLEN(L"\\bin")) == 0) | |
151 | out_len -= CONST_STRLEN(L"\\bin"); | |
152 | else if (out_len >= CONST_STRLEN(L"\\cmd") && | |
153 | wcsncmp(&out[out_len - CONST_STRLEN(L"\\cmd")], L"\\cmd", CONST_STRLEN(L"\\cmd")) == 0) | |
154 | out_len -= CONST_STRLEN(L"\\cmd"); | |
ec56af08 | 155 | |
e579e0f7 MB |
156 | if (!out_len) |
157 | return GIT_ENOTFOUND; | |
ec56af08 | 158 | |
e579e0f7 MB |
159 | out[out_len] = L'\0'; |
160 | return 0; | |
ec56af08 SS |
161 | } |
162 | ||
5540d947 | 163 | static int win32_find_existing_dirs( |
e579e0f7 MB |
164 | git_str* out, |
165 | const wchar_t* tmpl[]) | |
5540d947 | 166 | { |
e579e0f7 MB |
167 | git_win32_path path16; |
168 | git_str buf = GIT_STR_INIT; | |
5540d947 | 169 | |
e579e0f7 | 170 | git_str_clear(out); |
41954a49 RB |
171 | |
172 | for (; *tmpl != NULL; tmpl++) { | |
e579e0f7 MB |
173 | if (!git_win32__expand_path(path16, *tmpl) && |
174 | path16[0] != L'%' && | |
175 | !_waccess(path16, F_OK)) { | |
176 | win32_path_to_8(&buf, path16); | |
41954a49 RB |
177 | |
178 | if (buf.size) | |
e579e0f7 | 179 | git_str_join(out, GIT_PATH_LIST_SEPARATOR, out->ptr, buf.ptr); |
997579be SS |
180 | } |
181 | } | |
997579be | 182 | |
e579e0f7 | 183 | git_str_dispose(&buf); |
41954a49 | 184 | |
e579e0f7 | 185 | return (git_str_oom(out) ? -1 : 0); |
997579be | 186 | } |
5540d947 | 187 | |
e579e0f7 | 188 | static int append_subdir(git_str *out, git_str *path, const char *subdir) |
5540d947 | 189 | { |
e579e0f7 MB |
190 | static const char* architecture_roots[] = { |
191 | "", | |
192 | "mingw64", | |
193 | "mingw32", | |
194 | NULL | |
195 | }; | |
196 | const char **root; | |
197 | size_t orig_path_len = path->size; | |
198 | ||
199 | for (root = architecture_roots; *root; root++) { | |
200 | if ((*root[0] && git_str_joinpath(path, path->ptr, *root) < 0) || | |
201 | git_str_joinpath(path, path->ptr, subdir) < 0) | |
202 | return -1; | |
5540d947 | 203 | |
e579e0f7 MB |
204 | if (git_fs_path_exists(path->ptr) && |
205 | git_str_join(out, GIT_PATH_LIST_SEPARATOR, out->ptr, path->ptr) < 0) | |
206 | return -1; | |
5540d947 | 207 | |
e579e0f7 MB |
208 | git_str_truncate(path, orig_path_len); |
209 | } | |
5540d947 | 210 | |
e579e0f7 MB |
211 | return 0; |
212 | } | |
5540d947 | 213 | |
e579e0f7 MB |
214 | int git_win32__find_system_dirs(git_str *out, const char *subdir) |
215 | { | |
216 | git_win32_path pathdir, regdir; | |
217 | git_str path8 = GIT_STR_INIT; | |
218 | bool has_pathdir, has_regdir; | |
219 | int error; | |
220 | ||
221 | has_pathdir = (find_sysdir_in_path(pathdir) == 0); | |
222 | has_regdir = (find_sysdir_in_registry(regdir) == 0); | |
223 | ||
224 | if (!has_pathdir && !has_regdir) | |
225 | return 0; | |
226 | ||
227 | /* | |
228 | * Usually the git in the path is the same git in the registry, | |
229 | * in this case there's no need to duplicate the paths. | |
230 | */ | |
231 | if (has_pathdir && has_regdir && wcscmp(pathdir, regdir) == 0) | |
232 | has_regdir = false; | |
233 | ||
234 | if (has_pathdir) { | |
235 | if ((error = win32_path_to_8(&path8, pathdir)) < 0 || | |
236 | (error = append_subdir(out, &path8, subdir)) < 0) | |
237 | goto done; | |
238 | } | |
41954a49 | 239 | |
e579e0f7 MB |
240 | if (has_regdir) { |
241 | if ((error = win32_path_to_8(&path8, regdir)) < 0 || | |
242 | (error = append_subdir(out, &path8, subdir)) < 0) | |
243 | goto done; | |
244 | } | |
5540d947 | 245 | |
e579e0f7 MB |
246 | done: |
247 | git_str_dispose(&path8); | |
248 | return error; | |
5540d947 RB |
249 | } |
250 | ||
e579e0f7 | 251 | int git_win32__find_global_dirs(git_str *out) |
5540d947 | 252 | { |
5540d947 RB |
253 | static const wchar_t *global_tmpls[4] = { |
254 | L"%HOME%\\", | |
255 | L"%HOMEDRIVE%%HOMEPATH%\\", | |
256 | L"%USERPROFILE%\\", | |
257 | NULL, | |
258 | }; | |
259 | ||
c4ac556e | 260 | return win32_find_existing_dirs(out, global_tmpls); |
5540d947 RB |
261 | } |
262 | ||
e579e0f7 | 263 | int git_win32__find_xdg_dirs(git_str *out) |
5540d947 | 264 | { |
5540d947 RB |
265 | static const wchar_t *global_tmpls[7] = { |
266 | L"%XDG_CONFIG_HOME%\\git", | |
267 | L"%APPDATA%\\git", | |
268 | L"%LOCALAPPDATA%\\git", | |
269 | L"%HOME%\\.config\\git", | |
270 | L"%HOMEDRIVE%%HOMEPATH%\\.config\\git", | |
271 | L"%USERPROFILE%\\.config\\git", | |
272 | NULL, | |
273 | }; | |
274 | ||
c4ac556e | 275 | return win32_find_existing_dirs(out, global_tmpls); |
5540d947 | 276 | } |
8c7c5fa5 | 277 | |
e579e0f7 | 278 | int git_win32__find_programdata_dirs(git_str *out) |
8c7c5fa5 CMN |
279 | { |
280 | static const wchar_t *programdata_tmpls[2] = { | |
281 | L"%PROGRAMDATA%\\Git", | |
282 | NULL, | |
283 | }; | |
284 | ||
285 | return win32_find_existing_dirs(out, programdata_tmpls); | |
286 | } |